基於javascript的拖拽類封裝^o^

我是大哥的女朋友發表於2019-04-18

效果圖如下:

基於javascript的拖拽類封裝^o^
github地址如下: github地址

使用方法

引入js和對應的css

import Drag from '../../static/dragger.js'
import './assets/css/dragger.css'
複製程式碼

之後,例項化

new Drag({
    id: 'box-dragger',
    showAngle: true,
    isScale: false,
    showBorder: false
})
new Drag({
    id: 'box-dragger2',
    canZoom: false,
    canRotate: false
})
new Drag({
    id: 'img-box',
    showAngle: true,
    showPosition: true
    })
new Drag({
    id: 'test'
})
複製程式碼

具體實現(封裝細節)

功能細節整理:

  • 旋轉
  • 縮放
  • 平移

技術難點:

  • 旋轉時要注意盒子每一個點的位置發生了變化
  • 針對拖拽後的盒子的left和top都有變化,計算其left和top時需將其按照中心軸旋轉擺正,再進行計算
  • 當且僅有一個盒子是被選中的盒子,點選哪個選中哪個。(當前頁面多個例項化Drag物件時,如何保證操作互不影響)
  • 實現的兩種不同方式:
    • 可以選中某元素,直接給該元素內部加上操作的點
    • 有一個pannel,選中某元素時,將這個pannel定位到該元素的位置上

這兩種方式都實現過一次,第一種比較簡單,但是第一種,不好控制選中某個元素才讓操作點展示。

如何封裝:

考慮如何讓使用者快速上手使用,可參考的點:

  • 使用者需要傳入什麼必須的引數
  • 暴露給使用者什麼可設定的引數和方法

實現過程:

可配置引數

欄位 說明 是否必填 預設值
id 目標元素id
container 父容器id body
canRotate 是否可以旋轉 true
canZoom 是否可以縮放 true
canPull 是否可以拉昇 true
canMove 是否可以平移 true
showAngle 展示角度 false
showPosition 展示位置 false
isScale 是否等比例縮放 true
showBorder 是否展示pannel的border false

屬性

  • canRotate
  • canZoom
  • canPull
  • canMove
  • showAngle
  • isScale
  • id
  • container
  • targetObj
  • pannelDom 操作divdom
  • ...

具體看圖:

基於javascript的拖拽類封裝^o^

程式碼解說

  1. 初始化引數

  2. 初始化目標dom物件的位置:記錄其:

    • left平距左
    • top
    • width
    • height
    • angle
    • rightBottomPoint 目標dom物件右下座標
    • rightTopPoint 目標dom物件右上座標
    • leftTopPoint 目標dom物件左上座標
    • leftBottomPoint 目標dom物件左下座標
    • leftMiddlePoint 目標dom物件左中座標
    • rightMiddlePoint 目標dom物件右中座標
    • topMiddlePoint 目標dom物件上中座標
    • bottomMiddlePoint 目標dom物件下中座標
    • centerPos 目標dom物件中心點座標
  3. 初始化pannel結構
    當前的父容器中只有一個pannel結構,每次例項化物件時,會判斷一下如果當前這個父容器裡已經存在id為pannel的結構,就將其子節點清空,按照當前例項化物件傳進來的屬性重新渲染pannel子結構。如果沒有id為pannel的結構,就建立。

  4. 初始化事件

    • 給pannelDom和targetObj繫結mousedown事件
    • 給document繫結mousemove和mouseup事件
    initEvent () {
        document.addEventListener('mousemove', e => {
            e.preventDefault && e.preventDefault()
            this.moveChange(e, this.targetObj)
        })
        document.addEventListener('mouseup', e => {
            this.moveLeave(this.targetObj)
        })
        if (this.canMove) {
            // 外層給this.pannelDom新增mousedown事件,是在所有例項化結束後,panneldom被展示在最後一個例項化物件上,滑鼠按下它時,觸發moveInit事件
            this.pannelDom.onmousedown = e => {
                e.stopPropagation()
                this.moveInit(9, e, this.targetObj)
            }
            this.targetObj.onmousedown = e => {
                e.stopPropagation()
                this.moveInit(9, e, this.targetObj)
                this.initPannel()
                // 在點選其他未被選中元素時,pannel定位到該元素上,重寫pannelDom事件,因為此時的this.pannelDom已經根據新的目標元素被重寫
                this.pannelDom.onmousedown= e => {
                    this.moveInit(9, e, this.targetObj)
                }
            }
        }
    }
    複製程式碼
  5. dom操作

    • 旋轉操作
      • 滑鼠按下時,記錄當前滑鼠位置距離box中心位置的y/x的反正切函式A1。
        this.mouseInit = {
            x: Math.floor(e.clientX),
            y: Math.floor(e.clientY)
        }
        this.preRadian = Math.atan2(this.mouseInit.y - this.centerPos.y, this.mouseInit.x - this.centerPos.x) 
        複製程式碼
      • 滑鼠移動時,記錄再次計算滑鼠位置距離box中心位置的y/x的反正切函式A2。
        this.rotateCurrent = {
            x: Math.floor(e.clientX),
            y: Math.floor(e.clientY)
        }
        this.curRadian = Math.atan2(this.rotateCurrent.y - this.centerPos.y, this.rotateCurrent.x - this.centerPos.x)
        複製程式碼
      • 求A2-A1,求出移動的弧度
        this.tranformRadian = this.curRadian - this.preRadian
        複製程式碼
      • 求出最後box的旋轉角度,this.getRotate(target)是js中獲取某dom元素的旋轉角度的方法(貼上過來的,親測好使)
        this.angle = this.getRotate(target) +  Math.round(this.tranformRadian * 180 / Math.PI)
        this.preRadian = this.curRadian //滑鼠移動的每一下都計算這個角度,所以每一下移動前的弧度值都上一次移動後的弧度值
        複製程式碼
      • 計算旋轉後box每個點的座標,根據餘弦公式,傳入:旋轉前每點座標,旋轉中心座標和旋轉角度
        let disAngle = this.angle - this.initAngle
        this.rightBottomPoint = this.getRotatedPoint(this.initRightBottomPoint, this.centerPos, disAngle)
        this.rightTopPoint = this.getRotatedPoint(this.initRightTopPoint, this.centerPos, disAngle)
        this.leftTopPoint = this.getRotatedPoint(this.initLeftTopPoint, this.centerPos, disAngle)
        this.leftBottomPoint = this.getRotatedPoint(this.initLeftBottomPoint, this.centerPos, disAngle)
        this.leftMiddlePoint = this.getRotatedPoint(this.initLeftMiddlePoint, this.centerPos, disAngle)
        this.rightMiddlePoint = this.getRotatedPoint(this.initRightMiddlePoint, this.centerPos, disAngle)
        this.topMiddlePoint = this.getRotatedPoint(this.initTopMiddlePoint, this.centerPos, disAngle)
        this.bottomMiddlePoint = this.getRotatedPoint(this.initBottomMiddlePoint, this.centerPos, disAngle)
        複製程式碼
      複製程式碼
    • 沿著一個方向拉昇操作。
    • 沿著一個角縮放操作。 這兩個操作,主要參考了一個大佬的拖拽思想實現的 github wiki地址
  6. 優化,mousemove事件新增節流函式

    function throttle(fn, interval) {
        let canRun = true;
        return function () {
            if (!canRun) return;
            canRun = false;
            setTimeout(() => {
                fn.apply(this, arguments);
                canRun = true;
            }, interval);
        };
    }
    let that = this
    document.addEventListener('mousemove', throttle(function (e) {
        e.preventDefault && e.preventDefault()
        that.moveChange(e, that.targetObj)
    }, 10))
    複製程式碼

相關文章