基於javascript的拖拽類封裝^o^【不限制前端框架】

效果圖以下: css

github地址以下: github地址

使用方法

引入js和對應的css。真的有須要的小夥伴只要把我對應文件夾的dragger.js和dragger.css拷進本身的項目裏就能夠用啦,不限制前端框架啊,vue、react仍是html文件裏均可以使用哦html

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定位到該元素的位置上

這兩種方式都實現過一次,第一種比較簡單,可是第一種,很差控制選中某個元素才讓操做點展現。vue

如何封裝:

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

  • 用戶須要傳入什麼必須的參數
  • 暴露給用戶什麼可設置的參數和方法

實現過程:

可配置參數

字段 說明 是否必填 默認值
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
  • ...

具體看圖: git

代碼解說

  1. 初始化參數github

  2. 初始化目標dom對象的位置:記錄其:bash

    • 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. 初始化事件app

    • 給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))
    複製代碼
相關文章
相關標籤/搜索