摘要:参考了很多别人写的代码,最后终于弄明白了其中的原理,自己也写了一个。效果图如下地址如下拖拽类封装代码使用方法引入和对应的。如果没有为的结构,就创建。鼠标移动时,记录再次计算鼠标位置距离中心位置的的反正切函数。
在公司做一个h5编辑平台,中间需要对元素进行拖拽、放大缩小、旋转等操作,且需要对文本、图片、音乐组件等不同元素都可以具备这些功能。参考了很多别人写的代码,最后终于弄明白了其中的原理,自己也写了一个。
效果图如下:
github地址如下:拖拽类封装代码
使用方法引入js和对应的css。真的有需要的小伙伴只要把我对应文件夹的dragger.js和dragger.css拷进自己的项目里就可以用啦,不限制前端框架啊,vue、react还是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定位到该元素的位置上
这两种方式都实现过一次,第一种比较简单,但是第一种,不好控制选中某个元素才让操作点展示。
如何封装:考虑如何让用户快速上手使用,可参考的点:
用户需要传入什么必须的参数
暴露给用户什么可设置的参数和方法
实现过程: 可配置参数字段 | 说明 | 是否必填 | 默认值 |
---|---|---|---|
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)
沿着一个方向拉升操作。
沿着一个角缩放操作。
优化,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地址
补充节流函数和防抖函数的区别:
节流函数:在一定规定时间段触发过的事件就不再触发
防抖函数:触发了一次事件,如果在一定时间段内又触发一次,那么取消上一次触发,执行这次触发
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/109615.html
前言:前段时间负责公司的运营管理后台项目,通过运营后台的PC端拖拽配置布局,达到App首页模板的动态UI界面配置,生成页面。趁着周末,整理一下当时所了解到的拖拽。文章会根据大家的反馈或者自己学习经验的累积成长不定期更新丰富。如果你想了解更多PC端的拖拽开发,欢迎点赞关注或者收藏一波[鞠躬]。 之前在掘金一篇文章里看到这段话: UI 开发的三种模式 1.手写标签和样式代码,生成页面 2.可视化拖拽 ...
摘要:前面几篇文章,我跟大家分享了的一些基础知识,这篇文章,将会进入第一个实战环节利用前面几章的所涉及到的知识,封装一个拖拽对象。不封装对象直接实现利用原生封装拖拽对象通过扩展来实现拖拽对象。 showImg(https://segmentfault.com/img/remote/1460000008699587); 前面几篇文章,我跟大家分享了JavaScript的一些基础知识,这篇文章,...
摘要:原生实现对元素的拖拽一背景介绍此处为铺垫内容,可跳过随着前端的不断发展,各种各样的前端规范和新知识新技术层出不穷,极大地拓展了开发者的操作空间,也大大地提升了用户体验。 原生 JS 实现对 html 元素的拖拽 一、背景介绍 【此处为铺垫内容,可跳过】 随着 Web 前端的不断发展,各种各样的前端规范和新知识、新技术层出不穷,极大地拓展了开发者的操作空间,也大大地提升了用户体验。而随着...
摘要:原生实现对元素的拖拽一背景介绍此处为铺垫内容,可跳过随着前端的不断发展,各种各样的前端规范和新知识新技术层出不穷,极大地拓展了开发者的操作空间,也大大地提升了用户体验。 原生 JS 实现对 html 元素的拖拽 一、背景介绍 【此处为铺垫内容,可跳过】 随着 Web 前端的不断发展,各种各样的前端规范和新知识、新技术层出不穷,极大地拓展了开发者的操作空间,也大大地提升了用户体验。而随着...
摘要:拖拽排序组件地址因为使用了技术栈,所以封装优先考虑输入和输出。基于数据驱动去渲染页面控制拖拽元素的顺序。例如原生的事件,在里应使用事件。 拖拽排序组件Github地址:https://github.com/VicEcho/VD... 因为使用了react.js技术栈,所以封装优先考虑输入和输出。基于数据驱动去渲染页面、控制拖拽元素的顺序。 由于我不考虑兼容IE8等旧版本浏览器,拖拽的效...
阅读 2834·2023-04-25 17:59
阅读 674·2023-04-25 15:05
阅读 668·2021-11-25 09:43
阅读 3025·2021-10-12 10:13
阅读 3531·2021-09-27 13:59
阅读 3575·2021-09-23 11:21
阅读 3871·2021-09-08 09:35
阅读 560·2019-08-29 17:12