Vue-grid-layout实现web拖拽布局功能

 vue-grid-layout是一个vue栅格拖动布局的组件.官网

栅格布局,即网格布局,是一种新的 CSS 布局模型,比较擅长将一个页面划分为几个主要区域,以及定义这些区域的大小、位置、层次等关系。

与flex布局的区别:

  1. Flexbox 是一维布局系统,适合做局部布局,比如导航栏组件。
  2. 网格布局是二维布局系统,可以更好的操作行和列,通常用于整个页面的规划。
  3. 虽然 Flexbox 也可以用于大的页面布局,但是没有 网格布局强大和灵活。二者结合使用更加轻松。二者从应用场景来说并不冲突。
  4. flex 布局一次只能处理一个维度上的元素布局,一行或者一列(flex-direction)。网格布局是将容器划分成了“行”和“列”,产生了一个个的网格,我们可以将网格元素放在与这些行和列相关的位置上,从而达到我们布局的目的。

 vue-grid-layout优点:

  1. 元素可拖动

  2. 元素可调整大小

  3. 边界检查拖动和调整大小

  4. 可以添加或删除窗口小部件而无需重建网格

  5. 布局可以序列化

  6. 响应式布局

属性 GridLayout常用参数:

参数含义数据类型
layout栅格的初始布局Array
colNum栅格系统的列数Number 默认 12
rowHeight每行的高度Number 默认是单位是 px
isDraggable栅格中的元素是否可拖拽Boolean
isResizable是否可以改变大小Boolean
margin栅格中的元素边距Array

属性GridItem参数:

参数含义类型
iid类型不限
x格元素位于第几列Number
y元素位于第几行Number
w占几块(元素的初始宽度,值为colWidth的倍数)Number
h元素的初始高度,值为rowHeight的倍数。Number
isDraggable元素是否可拖拽Boolean
isResizable元素是否可以改变大小Boolean
static是否为静态的(无法拖拽、调整大小或被其他元素移动)Boolean

事件:

每一个栅格元素grid-item上都可以添加监听器,用于监听移动和调整大小事件,这样父级Vue对象就可以收到通知。

GridLayout:

事件

含义

 layoutCreatedEvent对应Vue生命周期的created
layoutBeforeMountEvent对应Vue生命周期的beforeMount
layoutMountedEvent对应Vue生命周期的mounted
 layoutReadyEvent 当完成mount中的所有操作时生成的事件
 layoutUpdatedEvent布局updated事件

GridItem:

事件

含义

moveRvent移动时的事件
resizeRvent调整大小时的事件
mocedRvent移动后的事件
resizedRvent调整大小后的事件
containerResizedRvent栅格元素/栅格容器更改大小的事件(浏览器窗口或其他)

示例:

<grid-layout
            :layout.sync="layout"
            :col-num="12"
            :row-height="30"
            :is-draggable="true"
            :is-resizable="true"
            :is-mirrored="false"
            :vertical-compact="true"
            :margin="[10, 10]"
            :use-css-transforms="true"
    >

        <grid-item v-for="item in layout"
                   :x="item.x"
                   :y="item.y"
                   :w="item.w"
                   :h="item.h"
                   :i="item.i"
                   :key="item.i">
                <span class="text">{{item.i}}</span>
                <span class="remove" @click="removeItem(item.i)">x</span>
        </grid-item>
    </grid-layout>
layout: [
                {"x":0,"y":0,"w":2,"h":2,"i":"0"},
                {"x":2,"y":0,"w":2,"h":4,"i":"1"},
                {"x":4,"y":0,"w":2,"h":5,"i":"2"},
                {"x":6,"y":0,"w":2,"h":3,"i":"3"},
            ],

源码解析:

1、栅格布局实现逻辑:

往grid-item上面放style样式,当标识是否使用CSS属性 useCssTransforms为true时,则使用transform来定位;(默认为true)

export function setTransform(top, left, width, height): Object {
	// Replace unitless items with px
	const translate = 'translate3d(' + left + 'px,' + top + 'px, 0)'
	return {
		transform: translate,
		WebkitTransform: translate,
		MozTransform: translate,
		msTransform: translate,
		OTransform: translate,
		width: width + 'px',
		height: height + 'px',
		position: 'absolute',
	}
}

否则使用position定位,设置子元素的top、 left值;

export function setTopLeft(top, left, width, height): Object {
	return {
		top: top + 'px',
		left: left + 'px',
		width: width + 'px',
		height: height + 'px',
		position: 'absolute',
	}
}

2、拖拽、缩放等交互;

使用的前端拖拽插件interact.js,提供拖,放,调整尺寸和多点触摸手势功能。

grid-item 通过interact的on方法,监听拖放、调整尺寸等事件。

         
    //将当前拖动元素经过interact实例化之后,即可监听相应的事件
    //拖拽事件          
    this.interactObj.on('dragstart dragmove dragend', function (event) {
     self.handleDrag(event)
    })


    //缩放事件
     this.interactObj.on('resizestart resizemove resizeend',function (event) {
         self.handleResize(event)
     })

//元素拖拽时

      handleDrag(event) {
      //如果禁止拖拽、调整大小则reture
      if (this.static) return
      if (this.isResizing) return
     
      const position = getControlPosition(event) // 从事件中获取当前拖动点。这是用作偏移量的。  
      // Get the current drag point from the event. This is used as the offset.
      if (position === null) return // not possible but satisfies flow
      const { x, y } = position

      // let shouldUpdate = false;
      let newPosition = { top: 0, left: 0 }
      //通过监听拖拽事件,将拖拽的位置赋值给newPosition
      switch (event.type) {
        case 'dragstart': {
          this.previousX = this.innerX
          this.previousY = this.innerY
                //元素的父级视图定位
          let parentRect =event.target.offsetParent.getBoundingClientRect()//offsetParent为离自身最近且经过定位的父元素
          //元素的视图定位
          let clientRect = event.target.getBoundingClientRect()
          //是否为镜像反转
          //计算出当前模块left与top的距离 
          if (this.renderRtl) {
            newPosition.left = (clientRect.right - parentRect.right) * -1
          } else {
            newPosition.left = clientRect.left - parentRect.left
          }
          newPosition.top = clientRect.top - parentRect.top
          this.dragging = newPosition
          console.log('dragstart====', this.dragging)
          this.isDragging = true
          break
        }
        case 'dragend': {
          if (!this.isDragging) return
          let parentRect = event.target.offsetParent.getBoundingClientRect()
          let clientRect = event.target.getBoundingClientRect()
          //                        Add rtl support
          if (this.renderRtl) {
            newPosition.left = (clientRect.right - parentRect.right) * -1
          } else {
            newPosition.left = clientRect.left - parentRect.left
          }
          newPosition.top = clientRect.top - parentRect.top
          // console.log("### drag end => " + JSON.stringify(newPosition));
          // console.log("### DROP: " + JSON.stringify(newPosition));
          this.dragging = null
          console.log('dragend====', this.dragging)
          this.isDragging = false
          // shouldUpdate = true;
          break
        }
        case 'dragmove': {
          const coreEvent = createCoreData(this.lastX, this.lastY, x, y)
          //                        Add rtl support
          if (this.renderRtl) {
            newPosition.left = this.dragging.left - coreEvent.deltaX
          } else {
            newPosition.left = this.dragging.left + coreEvent.deltaX
          }
          newPosition.top = this.dragging.top + coreEvent.deltaY

          this.dragging = newPosition
          console.log('dragmove====', this.dragging)
          break
        }
      }

      // Get new XY

      //根据left与top的计算出当前模块在XY的值(通过四舍五入的方式)
      let pos
      if (this.renderRtl) {
        pos = this.calcXY(newPosition.top, newPosition.left)
      } else {
        pos = this.calcXY(newPosition.top, newPosition.left)
      }

      this.lastX = x
      this.lastY = y
      //仅用来打印
      if (this.innerX !== pos.x || this.innerY !== pos.y) {
        this.$emit('move', this.i, pos.x, pos.y)
      }
      if (
        event.type === 'dragend' &&
        (this.previousX !== this.innerX || this.previousY !== this.innerY)
      ) {
        this.$emit('moved', this.i, pos.x, pos.y)
      }
      //将拖动时的位置数据传给容器,容器对layout与占位空间赋值
      this.eventBus.$emit(
        'dragEvent',
        event.type,
        this.i,
        pos.x,
        pos.y,
        this.innerH,
        this.innerW
      )
    },

// 拖渲染页面

      dragEvent: function (eventName, id, x, y, h, w) {

      //获取到当前的拖动的元素
      let l = getLayoutItem(this.layout, id)
      //GetLayoutItem sometimes returns null object
      if (l === undefined || l === null) {
        l = { x: 0, y: 0 }
      }
      //当拖动为dragmove、dragstart时,赋值占位符的位置
      if (eventName === 'dragmove' || eventName === 'dragstart') {
        this.placeholder.i = id
        this.placeholder.x = l.x
        this.placeholder.y = l.y
        this.placeholder.w = w
        this.placeholder.h = h
        this.$nextTick(function () {
          this.isDragging = true
        })
        //this.$broadcast("updateWidth", this.width);
        this.eventBus.$emit('updateWidth', this.width)
      } else {
        this.$nextTick(function () {
          this.isDragging = false
        })
      }

      // Move the element to the dragged location.
      //获取到其他元素的级联动作,将元素移动到拖动位置。
      this.layout = moveElement(
        this.layout,
        l,
        x,
        y,
        true,
        this.preventCollision//防止碰撞属性
      )
      compact(this.layout, this.verticalCompact)
      // needed because vue can't detect changes on array element properties
      //让vue检测到数组元素属性的变化
      this.eventBus.$emit('compact')
      this.updateHeight()
      if (eventName === 'dragend') this.$emit('layout-updated', this.layout)
    },

相关问题:

  1. 标识栅格元素是否可拖拽(isDraggable)该属性只能将元素向下排,并不能形成左右替换的形式,并且在该组件中也没有元素互相替换位置这种属性;
  2. GridItem的XY参数只能为整数;
  3. 没有下边界,发生碰撞的时候块会无限的往下移动;

总结:

    视图页面中每一个面板都可拖拽,可随意放大放小,体验还是不错的。

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值