随意拖拽、拉伸元素的功用是现在大热的自界说图表的重要组成功用,本文以最简略的视角搞懂随意拖拽、拉伸元素功用,完结这个功用需求先了解原生 drag && vue-ruler-tool && @smallwei/avue

demo在线体验地址:zhao-wenchao110.gitee.io/customdrag

一、了解HTML5原生拖拽 drag

1-1、了解拖拽事情的流程

其实拖拽功用的完结无非便是三个步骤:
选中元素 —> 拖动元素 —> 释放元素;

1-2、如何选中元素

HTML5中只需求将元素身上设置属性 draggabletrue,那么就能够按住鼠标左键选中元素,进行拖放了,其间 draggable的属性能够设置几个值:

  • true:允许拖动
  • false:禁止拖动
  • auto:跟随浏览器界说是否能够拖动
<div draggable="true"></div>

1-3、拖动事情

针对目标 事情名称 阐明
被拖动的元素 dragstart 在元素开始被拖动时分触发
drag 在元素被拖动时重复触发
dragend 在拖动操作完结时触发
目的地目标 dragenter 当被拖动元素进入目的地元素所占有的屏幕空间时触发
dragover 当被拖动元素在目的地元素内时触发
dragleave 当被拖动元素没有放下就离开目的地元素时触发

其间需求注意的是 dragenter && dragover两个事情,他们是回绝接纳所有被拖放的元素,所以在运用时需求 .preventDefault 阻挠默许的事情冒泡。

1-4、释放

针对目标 事情名称 阐明
目的地目标 drop 当被拖动元素在目的地元素里放下时触发,一般需求撤销浏览器的默许行为。

本文只能做最简略的知识点遍及,假如需求更深入了解原生drag事情,我个人推荐两篇文章

  • HTML5原生拖拽/拖放 Drag & Drop 详解

  • h5原生draggable拖拽事情详解

另外运用vue结构也能够运用二次封装的 vuedraggable

  • Vue.Draggable中文文档参阅 | 官方文档翻译

二、了解标尺功用 vue-ruler-tool

自界说图表--随意拖拽拉伸功用的完结
运用后便会在上边和左面出现标尺,如上图所见;

2-1、装置

npm install --save vue-ruler-tool

2-2、运用


<vue-ruler-tool
  v-model="dashboard.presetLine"
  class="vueRuler"
  :step-length="50"
  :parent="true"
  :position="'relative'"
  :is-scale-revise="true"
  :visible.sync="dashboard.presetLineVisible"
>
  <div></div>        
</vue-ruler-tool>
<script>
  import VueRulerTool from 'vue-ruler-tool'
  export default {
    components: {
      VueRulerTool
    },
  }
</script>

2-3、属性

parent

类型:Boolean

默许值:false

约束组件巨细在父级内部

<vue-ruler-tool :parent="true" >

position

类型:String

默许值: relative

或许值:['absolute', 'fixed', 'relative', 'static', 'inherit']

规定标尺工具的定位类型

<vue-ruler-tool :position="'fixed'" >
仿制代码

isHotKey

类型: Boolean

默许值: true

快捷键键开关,目前仅支持快捷键R标尺显现开关

<vue-ruler-tool :is-hot-key="ture" >
仿制代码

visible

类型:Boolean

默许值:true

是否显现,假如设为false则隐藏,可经过.sync接纳来自R快捷键的修正

<v-ruler :visible.sync="visible" >
data() {
  return {
    visible: true
  }
}

isScaleRevise

类型: Boolean

默许值: false

刻度修正(依据content进行刻度重置),意思便是从内容的方位开始从0计数

<vue-ruler-tool :is-scale-revise="ture" >
仿制代码

topSpacing

类型: Number

默许值: 0,

标尺与窗口的上间距,假如你的position不为fixed,此项必填

leftSpacing

类型: Number

默许值: 0

标尺与窗口的左间距,假如你的position不为fixed,此项必填

presetLine

类型: Array

默许值: []

承受格式:[{ type: 'l', site: 50 }, { type: 'v', site: 180 }]

预置参阅线l代表水平线,v代表垂直线,site为Number类型

<vue-ruler-tool :preset-line="[{ type: 'l', site: 100 }, { type: 'v', site: 200 }]" >
仿制代码

contentLayout

类型: Object

默许值: { top: 50, left: 50 }

内容部分布局分布,及内容摆放方位

<vue-ruler-tool :content-layout="{left:200,top:100}" >

2-4、办法

quickGeneration

参数:[{ type: 'l', site: 100 }, { type: 'v', site: 200 }]

快速设置参阅线,一般用来经过弹窗让用户输入

<vue-ruler-tool ref='rulerTool' >
let params=[
        { type: 'l', site: 100 },
        { type: 'v', site: 200 }
]
this.$refs.rulerTool.quickGeneration(params)

三、Avue

该组件运用文档地址:avuejs.com/default/dra…

3-1、装置

yarn add  @smallwei/avue -S # 或许:npm i @smallwei/avue -S

3-2、运用

// src/main
import Avue from '@smallwei/avue';
import '@smallwei/avue/lib/index.css';
Vue.use(Avue);

四、自界说拖拽 demo 完结

4-1、思路

首先咱们要了解自界说图表中拖拽功用的基本功用要求:

  • 每个组件图表拖拽到作业台中都是需求仿制的
  • 图表组件宽与高都是能够拉伸的
  • 组件的拖拽体验需求丝滑
  • 终究作业台的组件都是需求耐久化存储的

4-2、设置拖拽组件区域

template 部分

<ul class="col toolsBox">
  <!-- 组件区 -->
  <li v-for="item in arr1" :key="item.id" class="li" draggable="true" @dragstart="dragStartFn(item.id)" @dragend="dragEnd()">
    <div class="item">{{ item.name }}</div>
  </li>
  <li class="save" @click="saveWidgetsFn">
    保存
  </li>
</ul>

data中的数据

arr1: [
  { id: 'a', name: '1' },
  { id: 'b', name: '2' },
  { id: 'c', name: '3' },
  { id: 'd', name: '4' },
  { id: 'e', name: '5' }
],

1、固定组件依据data中的数据结构烘托,数据内最重要的是id,是用来判定组件的type品种,具有仅有性;
2、而且要给每个组件都设置 draggable="true" 表明为可拖拽;
3、设置 dragstart 元素被拖拽时触发事情存储组件仅有 id,在拖拽完结时触发 dragend 清空组件 id;

4-3、作业台区域

template 部分

<div class="col big-father">
  <!-- 标尺 -->
  <vue-ruler-tool
    v-model="dashboard.presetLine"  // 此处绑定辅助线坐标
    class="vueRuler"
    :step-length="50"  // 标尺间隔
    :parent="true"    // 约束组件巨细在父级内部
    :position="'relative'" 
    :is-scale-revise="true" // 刻度从 0 开始
    :visible.sync="dashboard.presetLineVisible" // 辅助线是否显现
  >
    <!-- 作业台 -->
    <div  //此元素是内部作业台区域规模
      id="workbench"
      class="workbench"
      @drop="widgetOnDraggedFn($event)" // 组件被拖拽到作业台中仿制一个新组件数据,而且将定位保存到其间
      @dragover="dragOverFn($event)" // 设置阻挠冒泡事情
      @mousedown.prevent="handleMousedown" // 设置点击作业台,撤销avue组件外部一圈的辅助线
    >
      // 该组件能够改变内部元素的宽高及定位
      <avue-draggable
        v-for="(item2,index2) in widgets" // widgets是作业台组件数据容器
        :key="index2" // 此处key用index,因为组件或许会重复运用,可是index不会重复,而且后面撤销avue组件外部一圈的辅助线需求index
        ref="draggableAvue" 
        // 子绝父相
        :index="index2" // 将仅有表明index绑定在组件身上,后面又用
        :width="item2.value.width"
        :height="item2.value.height"
        :left="item2.value.left"
        :top="item2.value.top"
        @focus="handleFocus" // 聚焦到组件时触发
        @blur="handleBlur" // 失焦时触发,在失焦时需求将终究调整好的宽高定位保存到data中的widgets,而且需求获取辅助线和移除辅助线
      >
        <div
          class="item2"
        >
          {{ item2.name }}
        </div>
      </avue-draggable>
    </div>
  </vue-ruler-tool>
</div>

js 部分

<script>
import VueRulerTool from 'vue-ruler-tool'
export default {
  components: {
    VueRulerTool
  },
  data() {
    return {
      // 界说要被拖拽目标的数组
      arr1: [
        { id: 'a', name: '1' },
        { id: 'b', name: '2' },
        { id: 'c', name: '3' },
        { id: 'd', name: '4' },
        { id: 'e', name: '5' }
      ],
      dragWidgetId: '', // 当时从工具栏拖拽的组件品种Id
      currentIndex: '', // 当时作业台上操作的组件index
      // 作业台大屏画布,保存到表gaea_report_dashboard中
      dashboard: {
        id: null,
        title: '', // 大屏页面标题
        backgroundColor: '', // 大屏背景色
        backgroundImage: '', // 大屏背景图片
        presetLine: [], // 辅助线
        presetLineVisible: true // 辅助线是否显现
      },
      // 大屏画布中的组件
      widgets: [
        // {
        //   // type和value终究存到数据库中去,保存到gaea_report_dashboard_widget中
        //   id: '',
        //   value: {
        //     width: 200,
        //     height: 200,
        //     left: 200,
        //     top: 200
        //   }
        // }
      ] // 作业区中拖放的组件
    }
  },
  created() {
    // 耐久化数据操作
    if (JSON.parse(localStorage.getItem('saveWidgetsFn'))) {
      this.widgets = JSON.parse(localStorage.getItem('saveWidgetsFn'))
    }
  },
  methods: {
    dragStartFn(id) {
      this.dragWidgetId = id
    },
    dragEnd() {
      this.dragWidgetId = ''
    },
    /* 拖拽到的内容区域 */
    widgetOnDraggedFn(e) {
      // 获取结束坐标和列名
      const eventX = e.clientX // 结束在屏幕的x坐标
      const eventY = e.clientY // 结束在屏幕的y坐标
      // 获取作业台 dom 的 top&left 定位
      const workbenchPosition = this.getDomTopLeftById('workbench')
      const widgetTopInWorkbench = eventY - workbenchPosition.top - 25
      const widgetLeftInWorkbench = eventX - workbenchPosition.left - 50
      // 找出仿制元素的 数据内容
      const obj = this.arr1.find(item => item.id === this.dragWidgetId)
      // 在作业台增加 一个新标签
      this.widgets.push({
        id: this.dragWidgetId,
        name: obj.name,
        value: {
          width: 100,
          height: 50,
          left: widgetLeftInWorkbench,
          top: widgetTopInWorkbench
        }
      })
    },
    dragOverFn(e) {
      e.preventDefault()
      e.stopPropagation()
    },
    // 获取dom在屏幕中的top和left
    getDomTopLeftById(id) {
      const dom = document.getElementById(id)
      let top = 0
      let left = 0
      if (dom != null) {
        top = dom.getBoundingClientRect().top
        left = dom.getBoundingClientRect().left
      }
      return { top: top, left: left }
    },
    // avue
    handleFocus({ index, left, top, width, height }) {
    },
    handleBlur({ index, left, top, width, height }) {
      this.$refs.draggableAvue[index].setActive(true)
      // 保存新增修正的组件
      if (left !== 0 && top !== 0) {
        this.widgets[index].value = {
          width,
          height,
          left,
          top
        }
      }
      // 判定假如 currentIndex 数组超过1个,则需求把上一个选中撤销,只保存下一个选中
      if (this.currentIndex !== index && this.currentIndex !== '') {
        this.$refs.draggableAvue[this.currentIndex].setActive(false)
      }
      this.currentIndex = index
    },
    // 存储 widgets
    saveWidgetsFn() {
      localStorage.setItem('saveWidgetsFn', JSON.stringify(this.widgets))
      alert('存储成功!')
    },
    handleMousedown() {
      if (this.currentIndex !== '') this.$refs.draggableAvue[this.currentIndex].setActive(false)
    }
  }
}
</script>

结语

本文只是简略的写了个demo完结了随意拖拽拉伸元素功用的完结,仅仅只是自界说图表很多复杂功用中的一环,以后还会更新其余功用的完结demo与思路,希望我们多多支持~!;

gitee 本文Demo的代码地址

封面优异开源的自界说图表,自己也是运用了该图表时所学甚多