-
Notifications
You must be signed in to change notification settings - Fork 293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
可视化拖拽组件库一些技术要点原理分析 #19
Comments
作者做的很棒 |
谢谢 |
这个能生成代码么 |
看一下第二篇 #20 |
请问一下我自己加了echart组件,为什么不能放大缩小呢 |
看看 echarts 组件能不能动态调整大小,画布组件的宽度高度我都是设成 100% 的。 |
厉害了 |
我在项目中添加了swiper组件。但是swiper组件自带左右滑动,所以在选中swiper的时候就无法选中该组件了。无法进行设置以及拖动 |
1 similar comment
我在项目中添加了swiper组件。但是swiper组件自带左右滑动,所以在选中swiper的时候就无法选中该组件了。无法进行设置以及拖动 |
自定义组件可以用一个假的组件来代替 swiper,等真正渲染时再用 swiper 代替。 |
感谢,我想问一下有什么办法让按钮,图片刷新后依然保持在编辑组件中而不被刷新掉 |
在哪里设置渲染的时候渲染真正的swiper的呢? |
页面上面有一个保存按钮,保存一下就可以了。如果不想手动保存,可以在修改快照的时候自动执行保存。 |
可以看一下鲁班 H5 的做法,它是将真正要渲染的组件放在右边,不放在画布中。 |
好的。谢谢回答。我去了解了解。 |
选中多个按钮或者图片为什么拖动不了多个呢 |
先点组合 |
感谢感谢 |
当一个组件是通过手动输入x坐标和y坐标的样式,拖动另外一个组件和它对齐之后,再拖动8个小圆点放大缩小会有很严重的bug,想了好久,不知道怎么解决。 |
做个动图复现一下 |
我描述错了,准确说是当直接修改组件的x y坐标的input值,再缩放这个组件就会出错 |
我试了一下修改坐标后,对组件进行缩放,没有问题。可以做个动图复现一下吗? |
这么好的项目,才1k多star,还有没有王法?还有没有法律? |
真特么牛逼 |
这个项目只是感兴趣研究的。 |
把复现步骤说一下,代码问题请在具体仓库下提 issue. |
如何不让组件拖到画布外呢 |
判断一下 x y 坐标和画布距离 |
牛!问下这个保存代码的部分,可以将拖拽好的页面保存成vue源码做二开吗? |
目前不支持这个功能
谭光志
***@***.***
…------------------ 原始邮件 ------------------
发件人: ***@***.***>;
发送时间: 2023年2月3日(星期五) 下午2:48
收件人: ***@***.***>;
抄送: ***@***.***>; ***@***.***>;
主题: Re: [woai3c/Front-end-articles] 可视化拖拽组件库一些技术要点原理分析 (#19)
牛!问下这个保存代码的部分,可以将拖拽好的页面保存成vue源码做二开吗?
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you authored the thread.Message ID: ***@***.***>
|
好的,谢谢 |
谭总太顶了 |
是不是要将自定义组件加到项目里才能用? |
对 |
请问如果要做tab组件或者容器组件,一个组件中拖入多个组件,这个有实现思路吗? |
这种容器需要支持嵌套组件才行,网上有很多,你可以看看这个 http://cdn.kcz66.com/k-form-design.html |
感谢大佬的分享,对我来说是很不错的教学,真的很棒 |
挂机 出去玩O(∩_∩)O
|
如何让每个组件添加上连线的功能 |
这得用 canvas 或者 svg 来做,这方面可以看下别的库。比如 x6 就挺不错的 https://x6.antv.vision/zh/examples/gallery。 |
谭总这个保存功预览链接是不起作用吗 |
有用的,我刚看了一下预览,保存(这是本地存储)和预览都没问题。 |
|
没实现,微信可以加 qq411020382 |
第一次直观的体会到什么是牛逼呜呜呜呜呜呜呜 |
作者受我一拜!!!学不完根本学不完!!!学无止境!!! |
挂机 出去玩O(∩_∩)O
|
加油! |
感谢您的好意,我这个项目就是教学用的,目前不算是产品。所以暂时不需要了,再次感谢。 |
挂机 出去玩O(∩_∩)O
|
作者有联系方式可以加一下么,想咨询个问题可以么 |
本文主要对以下技术要点进行分析:
为了让本文更加容易理解,我将以上技术要点结合在一起写了一个可视化拖拽组件库 DEMO:
建议结合源码一起阅读,效果更好(这个 DEMO 使用的是 Vue 技术栈)。
1. 编辑器
先来看一下页面的整体结构。
这一节要讲的编辑器其实就是中间的画布。它的作用是:当从左边组件列表拖拽出一个组件放到画布中时,画布要把这个组件渲染出来。
这个编辑器的实现思路是:
componentData
维护编辑器中的数据。push()
方法将新的组件数据添加到componentData
。v-for
指令遍历componentData
,将每个组件逐个渲染到画布(也可以使用 JSX 语法结合render()
方法代替)。编辑器渲染的核心代码如下所示:
每个组件数据大概是这样:
在遍历
componentData
组件数据时,主要靠is
属性来识别出真正要渲染的是哪个组件。例如要渲染的组件数据是
{ component: 'v-text' }
,则<component :is="item.component" />
会被转换为<v-text />
。当然,你这个组件也要提前注册到 Vue 中。如果你想了解更多
is
属性的资料,请查看官方文档。2. 自定义组件
原则上使用第三方组件也是可以的,但建议你最好封装一下。不管是第三方组件还是自定义组件,每个组件所需的属性可能都不一样,所以每个组件数据可以暴露出一个属性
propValue
用于传递值。例如 a 组件只需要一个属性,你的
propValue
可以这样写:propValue: 'aaa'
。如果需要多个属性,propValue
则可以是一个对象:在这个 DEMO 组件库中我定义了三个组件。
图片组件
Picture
:按钮组件
VButton
:文本组件
VText
:3. 拖拽
从组件列表到画布
一个元素如果要设为可拖拽,必须给它添加一个
draggable
属性。另外,在将组件列表中的组件拖拽到画布中,还有两个事件是起到关键作用的:dragstart
事件,在拖拽刚开始时触发。它主要用于将拖拽的组件信息传递给画布。drop
事件,在拖拽结束时触发。主要用于接收拖拽的组件信息。先来看一下左侧组件列表的代码:
可以看到给列表中的每一个组件都设置了
draggable
属性。另外,在触发dragstart
事件时,使用dataTransfer.setData()
传输数据。再来看一下接收数据的代码:触发
drop
事件时,使用dataTransfer.getData()
接收传输过来的索引数据,然后根据索引找到对应的组件数据,再添加到画布,从而渲染组件。组件在画布中移动
首先需要将画布设为相对定位
position: relative
,然后将每个组件设为绝对定位position: absolute
。除了这一点外,还要通过监听三个事件来进行移动:mousedown
事件,在组件上按下鼠标时,记录组件当前的位置,即 xy 坐标(为了方便讲解,这里使用的坐标轴,实际上 xy 对应的是 css 中的left
和top
。mousemove
事件,每次鼠标移动时,都用当前最新的 xy 坐标减去最开始的 xy 坐标,从而计算出移动距离,再改变组件位置。mouseup
事件,鼠标抬起时结束移动。PS: 有很多网友反馈拖拽的时候有卡顿现象,其实解决方案很简单,把浏览器的控制台关掉即可。
4. 删除组件、调整图层层级
改变图层层级
由于拖拽组件到画布中是有先后顺序的,所以可以按照数据顺序来分配图层层级。
例如画布新增了五个组件 abcde,那它们在画布数据中的顺序为
[a, b, c, d, e]
,图层层级和索引一一对应,即它们的z-index
属性值是 01234(后来居上)。用代码表示如下:如果不了解
z-index
属性的,请看一下 MDN 文档。理解了这一点之后,改变图层层级就很容易做到了。改变图层层级,即是改变组件数据在
componentData
数组中的顺序。例如有[a, b, c]
三个组件,它们的图层层级从低到高顺序为 abc(索引越大,层级越高)。如果要将 b 组件上移,只需将它和 c 调换顺序即可:
同理,置顶置底也是一样,例如我要将 a 组件置顶,只需将 a 和最后一个组件调换顺序即可:
删除组件
删除组件非常简单,一行代码搞定:
componentData.splice(index, 1)
。5. 放大缩小
细心的网友可能会发现,点击画布上的组件时,组件上会出现 8 个小圆点。这 8 个小圆点就是用来放大缩小用的。实现原理如下:
1. 在每个组件外面包一层
Shape
组件,Shape
组件里包含 8 个小圆点和一个<slot>
插槽,用于放置组件。Shape
组件内部结构:2. 点击组件时,将 8 个小圆点显示出来。
起作用的是这行代码
:active="item === curComponent"
。3. 计算每个小圆点的位置。
先来看一下计算小圆点位置的代码:
计算小圆点的位置需要获取一些信息:
height
、宽度width
注意,小圆点也是绝对定位的,相对于
Shape
组件。所以有四个小圆点的位置很好确定:left: 0, top: 0
left: width, top: 0
left: 0, top: height
left: width, top: height
另外的四个小圆点需要通过计算间接算出来。例如左边中间的小圆点,计算公式为
left: 0, top: height / 2
,其他小圆点同理。4. 点击小圆点时,可以进行放大缩小操作。
它的原理是这样的:
6. 撤消、重做
撤销重做的实现原理其实挺简单的,先看一下代码:
用一个数组来保存编辑器的快照数据。保存快照就是不停地执行
push()
操作,将当前的编辑器数据推入snapshotData
数组,并增加快照索引snapshotIndex
。目前以下几个动作会触发保存快照操作:...
撤销
假设现在
snapshotData
保存了 4 个快照。即[a, b, c, d]
,对应的快照索引为 3。如果这时进行了撤销操作,我们需要将快照索引减 1,然后将对应的快照数据赋值给画布。例如当前画布数据是 d,进行撤销后,索引 -1,现在画布的数据是 c。
重做
明白了撤销,那重做就很好理解了,就是将快照索引加 1,然后将对应的快照数据赋值给画布。
不过还有一点要注意,就是在撤销操作中进行了新的操作,要怎么办呢?有两种解决方案:
[a, b, c, d]
举例,假设现在进行了两次撤销操作,快照索引变为 1,对应的快照数据为 b,如果这时进行了新的操作,对应的快照数据为 e。那 e 会把 cd 顶掉,现在的快照数据为[a, b, e]
。[a, b, e, c, d]
。我采用的是第一种方案。
7. 吸附
什么是吸附?就是在拖拽组件时,如果它和另一个组件的距离比较接近,就会自动吸附在一起。
吸附的代码大概在 300 行左右,建议自己打开源码文件看(文件路径:
src\components\Editor\MarkLine.vue
)。这里不贴代码了,主要说说原理是怎么实现的。标线
在页面上创建 6 条线,分别是三横三竖。这 6 条线的作用是对齐,它们什么时候会出现呢?
具体的计算公式主要是根据每个组件的 xy 坐标和宽度高度进行计算的。例如要判断 ab 两个组件的左边是否对齐,则要知道它们每个组件的 x 坐标;如果要知道它们右边是否对齐,除了要知道 x 坐标,还要知道它们各自的宽度。
在对齐的时候,显示标线。
另外还要判断 ab 两个组件是否“足够”近。如果足够近,就吸附在一起。是否足够近要靠一个变量来判断:
小于等于
diff
像素则自动吸附。吸附
吸附效果是怎么实现的呢?
假设现在有 ab 组件,a 组件坐标 xy 都是 0,宽高都是 100。现在假设 a 组件不动,我们正在拖拽 b 组件。当把 b 组件拖到坐标为
x: 0, y: 103
时,由于103 - 100 <= 3(diff)
,所以可以判定它们已经接近得足够近。这时需要手动将 b 组件的 y 坐标值设为 100,这样就将 ab 组件吸附在一起了。优化
在拖拽时如果 6 条标线都显示出来会不太美观。所以我们可以做一下优化,在纵横方向上最多只同时显示一条线。实现原理如下:
可以发现,关键的地方是我们要知道两个组件的方向。即 ab 两个组件靠近,我们要知道到底 b 是在 a 的左边还是右边。
这一点可以通过鼠标移动事件来判断,之前在讲解拖拽的时候说过,
mousedown
事件触发时会记录起点坐标。所以每次触发mousemove
事件时,用当前坐标减去原来的坐标,就可以判断组件方向。例如 x 方向上,如果b.x - a.x
的差值为正,说明是 b 在 a 右边,否则为左边。8. 组件属性设置
每个组件都有一些通用属性和独有的属性,我们需要提供一个能显示和修改属性的地方。
我定义了一个
AttrList
组件,用于显示每个组件的属性。代码逻辑很简单,就是遍历组件的
style
对象,将每一个属性遍历出来。并且需要根据具体的属性用不同的组件显示出来,例如颜色属性,需要用颜色选择器显示;数值类的属性需要用type=number
的 input 组件显示等等。为了方便用户修改属性值,我使用
v-model
将组件和值绑定在一起。9. 预览、保存代码
预览和编辑的渲染原理是一样的,区别是不需要编辑功能。所以只需要将原先渲染组件的代码稍微改一下就可以了。
经过刚才的介绍,我们知道
Shape
组件具备了拖拽、放大缩小的功能。现在只需要将Shape
组件去掉,外面改成套一个普通的 DIV 就可以了(其实不用这个 DIV 也行,但为了绑定事件这个功能,所以需要加上)。保存代码的功能也特别简单,只需要保存画布上的数据
componentData
即可。保存有两种选择:在 DEMO 上我使用的
localStorage
保存在本地。10. 绑定事件
每个组件有一个
events
对象,用于存储绑定的事件。目前我只定义了两个事件:不过不能在编辑的时候触发,可以在预览的时候触发。
添加事件
通过
v-for
指令将事件列表渲染出来:选中事件时将事件添加到组件的
events
对象。触发事件
预览或真正渲染页面时,也需要在每个组件外面套一层 DIV,这样就可以在 DIV 上绑定一个点击事件,点击时触发我们刚才添加的事件。
11. 绑定动画
动画和事件的原理是一样的,先将所有的动画通过
v-for
指令渲染出来,然后点击动画将对应的动画添加到组件的animations
数组里。同事件一样,执行的时候也是遍历组件所有的动画并执行。为了方便,我们使用了 animate.css 动画库。
现在我们提前定义好所有的动画数据:
然后用
v-for
指令渲染出来动画列表。添加动画
点击动画将调用
addAnimation(animate)
将动画添加到组件的animations
数组。触发动画
运行动画的代码:
运行动画需要两个参数:组件对应的 DOM 元素(在组件使用
this.$el
获取)和它的动画数据animations
。并且需要监听animationend
事件和animationcancel
事件:一个是动画结束时触发,一个是动画意外终止时触发。利用这一点再配合
Promise
一起使用,就可以逐个运行组件的每个动画了。12. 导入 PSD
由于时间关系,这个功能我还没做。现在简单的描述一下怎么做这个功能。那就是使用 psd.js 库,它可以解析 PSD 文件。
使用
psd
库解析 PSD 文件得出的数据如下:从以上代码可以发现,这些数据和 css 非常像。根据这一点,只需要写一个转换函数,将这些数据转换成我们组件所需的数据,就能实现 PSD 文件转成渲染组件的功能。目前 quark-h5 和 luban-h5 都是这样实现的 PSD 转换功能。
13. 手机模式
由于画布是可以调整大小的,我们可以使用 iphone6 的分辨率来开发手机页面。
这样开发出来的页面也可以在手机下正常浏览,但可能会有样式偏差。因为我自定义的三个组件是没有做适配的,如果你需要开发手机页面,那自定义组件必须使用移动端的 UI 组件库。或者自己开发移动端专用的自定义组件。
总结
由于 DEMO 的代码比较多,所以在讲解每一个功能点时,我只把关键代码贴上来。所以大家会发现 DEMO 的源码和我贴上来的代码会有些区别,请不必在意。
另外,DEMO 的样式也比较简陋,主要是最近事情比较多,没太多时间写好看点,请见谅。
参考资料
The text was updated successfully, but these errors were encountered: