效果圖如下:
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
- ...
具體看圖:
程式碼解說
-
初始化引數
-
初始化目標dom物件的位置:記錄其:
- left平距左
- top
- width
- height
- angle
- rightBottomPoint 目標dom物件右下座標
- rightTopPoint 目標dom物件右上座標
- leftTopPoint 目標dom物件左上座標
- leftBottomPoint 目標dom物件左下座標
- leftMiddlePoint 目標dom物件左中座標
- rightMiddlePoint 目標dom物件右中座標
- topMiddlePoint 目標dom物件上中座標
- bottomMiddlePoint 目標dom物件下中座標
- centerPos 目標dom物件中心點座標
-
初始化pannel結構
當前的父容器中只有一個pannel結構,每次例項化物件時,會判斷一下如果當前這個父容器裡已經存在id為pannel的結構,就將其子節點清空,按照當前例項化物件傳進來的屬性重新渲染pannel子結構。如果沒有id為pannel的結構,就建立。 -
初始化事件
- 給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) } } } } 複製程式碼
-
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) 複製程式碼
複製程式碼
- 滑鼠按下時,記錄當前滑鼠位置距離box中心位置的y/x的反正切函式A1。
- 沿著一個方向拉昇操作。
- 沿著一個角縮放操作。 這兩個操作,主要參考了一個大佬的拖拽思想實現的 github wiki地址
- 旋轉操作
-
優化,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)) 複製程式碼