在公司作一個h5編輯平臺,中間須要對元素進行拖拽、放大縮小、旋轉等操做,且須要對文本、圖片、音樂組件等不一樣元素均可以具有這些功能。參考了不少別人寫的代碼,最後終於弄明白了其中的原理,本身也寫了一個。css
效果圖以下:html
github地址以下:拖拽類封裝代碼前端
引入js和對應的css。真的有須要的小夥伴只要把我對應文件夾的dragger.js和dragger.css拷進本身的項目裏就能夠用啦,不限制前端框架啊,vue、react仍是html文件裏均可以使用哦vue
import Drag from '../../static/dragger.js' import './assets/css/dragger.css'
以後,實例化react
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' })
實現的兩種不一樣方式:git
這兩種方式都實現過一次,第一種比較簡單,可是第一種,很差控制選中某個元素才讓操做點展現。github
考慮如何讓用戶快速上手使用,可參考的點:前端框架
字段 | 說明 | 是否必填 | 默認值 |
---|---|---|---|
id | 目標元素id | 是 | 無 |
container | 父容器id | 否 | body |
canRotate | 是否能夠旋轉 | 否 | true |
canZoom | 是否能夠縮放 | 否 | true |
canPull | 是否能夠拉昇 | 否 | true |
canMove | 是否能夠平移 | 否 | true |
showAngle | 展現角度 | 否 | false |
showPosition | 展現位置 | 否 | false |
isScale | 是否等比例縮放 | 否 | true |
showBorder | 是否展現pannel的border | 否 | false |
具體看圖:app
初始化目標dom對象的位置:記錄其:框架
初始化事件
給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)
優化,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))
中間的沿着一個方向的拉昇操做和沿着一個角的縮放操做主要是參考了一個大佬的拖拽思想實現的 github wiki地址
補充節流函數和防抖函數的區別: