vue-draggable-resizable 用于可调整大小和可拖动元素(一)

  • Post author:
  • Post category:vue


想把div拖来拖去,又想对它进行大小控制,如果再美化点,做成像PS那样的,图形处理方式的,还有右击显示菜单的,就更好了,有没有呢,有,它就是 vue-draggable-resizable

感谢vue-draggable-resizable团队,解决我等码农的需求

这是我做出来的效果

下面开始步骤了

1.安装或者组件化

我是选择了私有组件化,因为我对它进行了二次开发,下面是直接新建文件夹放进去的的插件,(好处是与版本管理脱节,完全放心的DIY,化为了私有的)

2.有了插件,现在开始写字啦

建一个封装后的组件

内容

<!--节目编辑/图片拖拽组件-->
<template>
  <vue-draggable-resizable
    :w="item.width"
    :h="item.height"
    :x="item.x"
    :y="item.y"
    :z-index="id"
    :is-conflict-check="false"
    :snap="true"
    :snap-tolerance="10"
    :grid="[1,1]"
    :parent-scale="parentScale"
    :resizable="isresizable"
    :draggable="isdraggable"
    :parent="false"
    class="img-drag-item"
    @dragging="onDrag"
    @dragstop="onDragstop"
    @resizing="onResize"
    @resizestop="onResizestop"
    @activated="onActivated"
    @deactivated="onDeActivated"
  >

    <div
      v-if="item.type ===1"
      :style="styleMediaObj"
      class="middle-title"
      @click="changeliveisResize(true)"
    >
      <!-- 图文 -->
      <div
        v-if="!bgcImgUrl"
        :style="{backgroundColor: hexToRGBA(item.bgColor,item.bgOpacity),height:item.height - item.fontSize - 10 + 'px'}"
        class="text-son">{{ $t('programModel.imageText') }}</div>
      <div v-else>
        <div :style="{backgroundSize: '100% 100%',backgroundImage: 'url(' + bgcImgUrl + ')',height:item.height - item.fontSize - 10 + 'px'}" class="text-son"></div>
      </div>
      <div :style="{backgroundColor: hexToRGBA(item.bgColor,item.bgOpacity), padding:'3px',fontSize: item.fontSize + 'px', color: item.fontColor,height:item.fontSize+10+'px'}" class="left-align-text caption-text">
        <font :face="item.font"> {{ item.text }} </font>
      </div>
    </div>

    <!-- 图片 -->
    <div
      v-else-if="item.type ===2"
      :style="styleMediaObj2"
      class="middle-title"
      @click="changeliveisResize(true)"
    >
      <div v-show="!bgcImgUrl" class="text-son">{{ $t('program.image') }}</div>
    </div>

    <!-- 文本 -->
    <div
      v-else-if="item.type ===3"
      :style="styleObj"
      style="border-radius: 5px"
      class="left-title bgc-opacity"
      @click="changeliveisResize(true)">
      <div v-if="!item.text">
        <div class="text-son">
          <font :face="item.font">
            {{ $t('program.text') }}
          </font>
        </div>
      </div>
      <div v-else>
        <div :style="{fontSize: item.fontSize + 'px', color: item.fontColor}" class="left-align-text">
          <font :face="item.font">{{ item.text }}</font>
        </div>
      </div>
    </div>

    <!-- 字幕 -->
    <div
      v-else-if="item.type ===4"
      :style="styleObj"
      style="border-radius: 5px"
      class="left-title bgc-opacity"
      @click="changeliveisResize(true)">
      <div v-if="!item.text">
        <div class="text-son">
          <font :face="item.font">
            {{ $t('program.caption') }}
          </font>
        </div>
      </div>
      <div v-else>
        <div :style="{fontSize: item.fontSize + 'px', color: item.fontColor}" class="left-align-text caption-text">
          <font :face="item.font">
            {{ item.text }}
          </font>
        </div>
      </div>
    </div>

    <!-- 容器 -->
    <div
      v-else-if="item.type ===5"
      :style="{backgroundColor: hexToRGBA(item.bgColor,item.bgOpacity)}"
      class="middle-title"
      @click="changeliveisResize(true)"
    >
      <div class="text-son">{{ $t('programModel.classContainer') }}</div>
    </div>

    <!-- 滚动图-->
    <div
      v-else-if="item.type ===6"
      :style="styleMediaObj2"
      class="middle-title"
      @click="changeliveisResize(true)"
    >
      <!-- 多媒体 -->
      <div v-show="!bgcImgUrl" class="text-son">{{ $t('programModel.banner1') }}</div>
    </div>
  </vue-draggable-resizable>
</template>

<script>
import VueDraggableResizable from '@/components/draggable-resizable/components/vue-draggable-resizable'
import '@/components/draggable-resizable/components/vue-draggable-resizable.css'
// import VueDraggableResizable from 'vue-draggable-resizable-gorkys'
// import 'vue-draggable-resizable-gorkys/dist/VueDraggableResizable.css'
import { parseTime2 } from '@/prototypeEx'
export default {
  name: 'DragableItem',
  components: {
    VueDraggableResizable
  },
  filters: {
    // parseTime(v) {
    //   return parseTime(v)
    // }
    //
  },
  props: {
    id: {
      type: Number,
      required: true
    },
    item: {
      type: Object,
      default: () => ({})
    },
    scale: {
      type: Number,
      default: 1
    },
    isresizable: {
      type: Boolean,
      required: true,
      default: true
    },
    heightisreadonly: {
      type: Boolean,
      required: true,
      default: false
    },
    widthisreadonly: {
      type: Boolean,
      required: true,
      default: false
    },
    isdraggable: {
      type: Boolean,
      required: true,
      default: true
    }
  },
  data() {
    return {
      imgItem: {
        width: this.item.width,
        height: this.item.height,
        x: this.item.x,
        y: this.item.y,
        id: this.item.id
      },
      show: true,
      parentScale: this.scale,
      bgcImgUrl: '' // 模块背景图片地址
    }
  },
  computed: {
    styleObj() {
      // 需要将十六进制颜色和透明度转换为rgba格式,背景透明度才不会影响内部的文本元素
      return {
        backgroundColor: this.hexToRGBA(this.item.bgColor, this.item.bgOpacity)
      }
    },
    styleMediaObj() {
      if (this.bgcImgUrl) {
        return {
          backgroundSize: '100% 100%'
        }
      }
    },

    styleMediaObj2() {
      if (this.bgcImgUrl) {
        return {
          backgroundImage: 'url(' + this.bgcImgUrl + ')',
          backgroundSize: '100% 100%'
        }
      } else {
        return {
          backgroundColor: this.hexToRGBA(this.item.bgColor, this.item.bgOpacity)
        }
      }
    },

    today() {
      const time = new Date()
      return parseTime2(time, 'yyyy-MM-dd HH:mm:ss EEEE')
    }
  },
  watch: {
    // 深度监听模块有没有选中素材元素,有就需要更新模块背景图片地址
    item: {
      immediate: true, // fix 第一次获取数据不加载背景图片的bug
      handler: function(val) {
        if (this.item.pic || (this.item.picUnitList && this.item.picUnitList.length > 0)) {
          this.baseCallback(this.item.pic ? this.item.pic : this.item.picUnitList[0].pic)
        } else {
          this.baseCallback()
        }
        this.item.text = val.text
        this.imgItem.x = val.x
        this.imgItem.y = val.y
        this.imgItem.width = val.width
        this.imgItem.height = val.height
        console.log(this.imgItem.y)
      },
      deep: true
    },
    scale(val, oldVal) {
      if (val === oldVal) {
        return
      }
      this.parentScale = val
    }
  },
  mounted() {
  },
  created() {
    var that = this
    document.onkeydown = function(e) {
      const e1 = e || event || window.event
      // 键盘按键判断:左箭头-37;上箭头-38;右箭头-39;下箭头-40
      // 左
      if (e1 && e1.keyCode == 37) {
        that.$emit('changingData_keypress', {
          x: -1,
          y: 0
        })
      } else if (e1 && e1.keyCode == 38) {
        that.$emit('changingData_keypress', {
          x: 0,
          y: -1
        })
      } else if (e1 && e1.keyCode == 39) {
        that.$emit('changingData_keypress', {
          x: 1,
          y: 0
        })
      } else if (e1 && e1.keyCode == 40) {
        that.$emit('changingData_keypress', {
          x: 0,
          y: 1
        })
      }
    }
  },
  methods: {
    today_format(fmt) {
      const time = new Date()
      return parseTime2(time, fmt)
    },
    activeData() {
      // 向父组件实时更新前块的数据信息
      this.$emit('changingData', {
        x: this.imgItem.x,
        y: this.imgItem.y,
        width: this.imgItem.width,
        height: this.imgItem.height,
        type: this.item.type,
        clickType: this.item.clickType > 0 ? this.item.clickType : undefined,
        clickTarget: this.item.clickTarget ? this.item.clickTarget : undefined,
        pageRolling: this.item.pageRolling >= 0 ? this.item.pageRolling : 0,
        id: this.id
      })
    },

    changeliveisResize(flag) {
      this.$emit('changeisresizable', flag)
    },

    // 拖拽结束,尺寸修改结束后向父组件传递确定的位置信息
    updateData() {
      if (this.item.type === 10) {
        this.$emit('updateFinalData', {
          x: this.imgItem.x,
          y: this.imgItem.y,
          width: this.imgItem.width,
          height: this.imgItem.height,
          id: this.id
        })
      } else {
        this.$emit('updateFinalData', {
          x: this.imgItem.x,
          y: this.imgItem.y,
          width: this.imgItem.width,
          height: this.imgItem.height,
          id: this.id
        })
      }
    },
    onResize: function(x, y, width, height) {
      if (this.item.type === 10) {
        this.imgItem.x = x
        this.imgItem.y = y
        this.imgItem.width = width
        this.imgItem.height = height
      } else {
        this.imgItem.x = x
        this.imgItem.y = y
        this.imgItem.width = width
        this.imgItem.height = height
      }
      this.activeData()
      this.$emit('noclonedate')
    },
    // 改变尺寸大小结束后
    onResizestop() {
      this.updateData()
    },
    onDrag: function(x, y) {
      this.imgItem.x = x
      this.imgItem.y = y
      this.activeData()
      this.$emit('noclonedate')
    },
    // 拖拽结束后
    onDragstop() {
      this.updateData()
    },
    deleteStudent: function() {
      if (this.index !== 0) {
        this.show = false
      }
    },
    onActivated() {
      this.activeData()
    },
    onDeActivated() {
      this.activeData()
    },
    // 将网络请求的图片转换成base64字符
    getBase64(url) {
      return new Promise((resolve, reject) => {
        var Img = new Image()
        var dataURL = ''
        Img.setAttribute('crossOrigin', 'Anonymous') // 跨域图片能正常裁剪
        Img.src = url + '?v=' + Math.random()
        Img.onload = function() { // 对象初始化加载完成后
          // 要先确保图片完整获取到,这是个异步事件
          var canvas = document.createElement('canvas') // 创建canvas元素
          var width = Img.width // 确保canvas的尺寸和图片一样
          var height = Img.height
          canvas.width = width
          canvas.height = height
          canvas.getContext('2d').drawImage(Img, 0, 0, width, height) // 将图片绘制到canvas中
          dataURL = canvas.toDataURL('image/png') // 转换图片为dataURL
          resolve(dataURL)
        }
      })
    },
    // 获取base64图片数据赋值给属性
    async baseCallback(url) {
      try {
        if (!url) {
          // 多媒体素材一个都没有了,背景色应还原
          this.bgcImgUrl = ''
          return
        }
        const res = await this.getBase64(url) // 这种方案是将网络图片转换为base64格式再做背景显示处理,缺陷是对png格式处理不了
        this.bgcImgUrl = res
      } catch (err) {
        console.log(err)
      }
    },

    /**
     * 十六进制颜色转换为带透明度的颜色
     * @param _color 十六进制颜色
     * @param _opactity 透明度
     * @returns {string} rgba
     */
    hexToRGBA(_color, _opacity) {
      let sColor = _color.toLowerCase()
      // 十六进制颜色值的正则表达式
      const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
      // 如果是16进制颜色
      if (sColor && reg.test(sColor)) {
        if (sColor.length === 4) {
          let sColorNew = '#'
          for (let i = 1; i < 4; i += 1) {
            sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
          }
          sColor = sColorNew
        }
        // 处理六位的颜色值
        const sColorChange = []
        for (let i = 1; i < 7; i += 2) {
          sColorChange.push(parseInt('0x' + sColor.slice(i, i + 2)))
        }
        return 'rgba(' + sColorChange.join(',') + ',' + _opacity + ')'
      }
      return sColor
    }
  }
}
</script>

<style  lang="scss" scoped>

.img-drag-item {
  // border: 1px solid #67c23a;
  border: 1px solid rgba(0,0,0,0);
  // background-color: #f7fdf9;
  // display: table; // fix firefox背景图不显示的bug
  position: relative;
  box-sizing: border-box;
  cursor: move;
  .middle-title {
    vertical-align: middle;
    text-align: center;
    height: 100%;
    width: 100%;
    font-size: 30px;
    border-radius: 5px;
    // background-color: #f7fdf9;
    overflow: hidden;
    .text-son {
      height: 100%;
      font-size: 1rem;
      position: relative;
    }
    .text-son::before {
      display: inline-block;
      content: '';
      height: 100%;
      vertical-align: middle;
    }
  }
  .bgc-color {
    background-color: #f7fdf9;
  }
  .left-title {
    height: 100%;
    width: 100%;
    font-size: 30px;
    background-color: #f7fdf9;
    overflow: hidden;
    .text-son {
      font-size: 1rem;
    }
    .text-son::before {
      display: inline-block;
      content: '';
      height: 100%;
      vertical-align: middle;
    }
  }
  .left-align-text {
    text-align: left;
      word-wrap:break-word;
    word-break:break-all;

  }
  .caption-text {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
  .bgc-opacity {
    background-color:transparent;
    // opacity: 0.1;
  }
}
// 块激活样式
.active {
  border: 1px solid #67c23a;
}
</style>



版权声明:本文为u012174809原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。