一个简单的可视化拖拽功能的实现

10,196

一个简单的可视化拖拽功能的实现

前言:本人刚从大学毕业,步入工作一个月,公司领导要求开发一个可拖拽页面,开发一周后,我想总结一下开发过程中的技术和踩的一些坑。肯定很多大佬这个还是很简单的,如果各位大佬有更好的方法可以给我建议。

本文主要对以下技术要点进行分析:

这里因为公司要合到项目里面,所以要求不能用vue脚手架,我只是引入了vue.js来开发。

1.左边拖拽

2.根据左边拖拽在画布上对应显示(包括矩形、图片、视频)

3.画布上元素拖拽

4.放大、缩小

5.组件属性(包括宽高、字体大小、删除等)

6.天气和时间

7.预览

以矩形拖拽为例来演示:

矩形拖拽演示.gif

项目预览(pc端):

meteor520.3vzhuji.net/

一、左边拖拽

这部分的拖拽主要运用的是HTML5 的拖拽属性并结合拖拽的一些时间来进行开发

首先要让元素触发拖动事件,需要给被拖动元素设置draggable="true"属性,并且给准备拖动到的目标区域设置drop事件,这样便能控制拖动过程的触发的时段。

1、发生在被拖动的元素上的事件

事件名触发时机
dragstart当拖动开始时(按下鼠标键并开始移动)触发一次
drag拖动开始后触发,在元素拖动期间会持续触发
dragend拖动结束(释放鼠标、无论放置目标是否有效)后触发一次

2、发生在放置目标元素上的事件

事件名触发时机
dragenter当拖动元素进入目标时触发
dragover当拖动元素在目标元素范围内移动时持续触发
dragleave元素拖出了放置目标,则dragover事件不再发生,触发dragleave事件
drop如果拖动元素放到了目标元素上,则触发drop事件

我这里拖拽的时候给矩形、图片、视频都设置了一个name属性,和后面定义在画布上的组件名一样,具体作用后面点说,这里我主要就是利用dragstart和drop事件来进行开发,主要代码如下图所示:

  dragstart(ev) {
                    ev.dataTransfer.setData(" Text ", ev.target.id);
                    // 获取设置的name属性
                    this.saveData = ev.target.getAttribute('name')
                },
                
                drop(ev) {
                    // 阻止默认事件
                    ev.preventDefault();
                    let length = $('.videocpn').length;
                    if (this.selectedData.indexOf('myvideo') !== -1 && this.saveData == 'myvideo' && length !== 0) {
                        this.saveData = ""
                        alert('只能插入一个视频')
                        return
                    }
                        // 把在dragstart事件中得到的saveData赋值给selectedData
                    this.selectedData.push(this.saveData);

                    //    清空saveData

                    this.saveData = "";

                },

二、根据左边拖拽在画布上对应显示

刚刚上面说了我给每一个左边需要拖动的组件都给了一个name属性,而且是和我中间给它们对应的组件名一样的。因为这里用到了vue的内置组件component来对应,在左边拖拽的时候获取到当面拖拽的name属性,在drop放下的时候把它添加到一个数组里面,然后在component遍历并把遍历的值赋值给is就可以对应渲染到画布上了,这里需要注意的是name属性要和画布中显示的组件名字一样。

<component  v-for="(item,index) in selectedData" :key="index" :is="item">
    </component>

三、画布上元素拖拽

画布上元素的拖拽就很简单了,这里我用来easyUi来进行拖拽功能实现,当然,如果你要用原生js来写也是可以的,我也试了一下,主要就是通过鼠标onmousedown、onmousemove、onmouseup事件来监听位置,再通过clientX、screenX、offsetX、pageX等来配合进行拖拽。

坑点: 在开发矩形拖拽功能的时候,如果要实现矩形页面上的可编辑,因为要选中文字,所以和拖拽产生了冲突,导致不能编辑。 解决办法: 1.可以在矩形上给出一定区域的拖拽空间,而不是全部区域都可以进行拖拽。 2.我最后选择的解决方案就是在后面属性设置的时候给了一个内容输入框,编辑就在内容输入框里实现

四、放大、缩小

缩放功能也是用easyui实现的。这个没啥好说的

五、组件属性

这部分就是实现矩形、图片和视频的属性设置功能开发,这部分主要解决的问题就是需要当拖动了多个组件时,点击某一个组件,要一一对应数据。这里我以矩形实现步骤为例来说一下实现步骤:

1.首先我是写好了属性组件的内容布局和样式,效果为:

image.png

2.在组件的mounted生命周期中我给每一个矩形都设置了唯一的id

 mounted() {
                let index = $('.rectanglecpn').length - 1;
                let allRectanglecpn = $('.rectanglecpn')
                let children = allRectanglecpn[index].children
                id = 'eidtableArea' + index
                    // 给当前矩形设置ID
                children[0].setAttribute('id', id)

            },

3.有了唯一的id后,在点击矩形时,右边属性设置区域就会显示第一步弄好的属性组件,但是现在是没有数据的,所以这一步要把点击矩形对应的当前id、宽高等属性传递过去,因为矩形和属性是两个兄弟组件,所以我需要在矩形组件里面设置一个data为rectangleStyle,再把rectangleStyle子组件传给父组件data名为rectangleAllStyle里,然后再通过父组件传子组件传给右边矩形属性组件,这里用到vue的父子传值和子父传值。点击矩形的代码为:

 let id = e.target.getAttribute('id');
                    // 获取样式值
                    let myDiv = e.target.parentNode;
                    finalStyle = myDiv.currentStyle ? myDiv.currentStyle : document.defaultView.getComputedStyle(myDiv, null);
                    this.rectangleStyle.rectangleContent = e.target.innerText;
                    this.rectangleStyle.currentId = id;
                    this.rectangleStyle.width = finalStyle.width.replace('px', '');
                    this.rectangleStyle.height = finalStyle.height.replace('px', '');
                    this.rectangleStyle.lineHeight = parseInt(finalStyle.lineHeight.replace('px', ''));
                    this.rectangleStyle.fontSize = finalStyle.fontSize.replace('px', '');
                    this.rectangleStyle.fontWeight = finalStyle.fontWeight;
                    this.rectangleStyle.alignment = finalStyle.textAlign;
                    // rgba转换为16进制
                    function hexify(color) {
                        var values = color
                            .replace(/rgba?\(/, '')
                            .replace(/\)/, '')
                            .replace(/[\s+]/g, '')
                            .split(',');
                        var a = parseFloat(values[3] || 1),
                            r = Math.floor(a * parseInt(values[0]) + (1 - a) * 255),
                            g = Math.floor(a * parseInt(values[1]) + (1 - a) * 255),
                            b = Math.floor(a * parseInt(values[2]) + (1 - a) * 255);
                        return "#" +
                            ("0" + r.toString(16)).slice(-2) +
                            ("0" + g.toString(16)).slice(-2) +
                            ("0" + b.toString(16)).slice(-2);
                    }
                    const color = hexify(finalStyle.color);
                    const backgroundColor = hexify(finalStyle.backgroundColor);
                    this.rectangleStyle.color = color;
                    this.rectangleStyle.backgroundColor = backgroundColor;
                    $('.attrModuleNullcpn').hide();
                    $('.imageAttrcpn').hide();
                    $('.rectangleAttrcpn').show();
                    // vue子传父,定义自定义事件,这里发射,然后去父组件接收
                    rectangleStyle = this.rectangleStyle;
                    this.$emit('rectangleclick', rectangleStyle)

4.现在矩形属性组件里面的数据渲染进来,所以当属性设置的时候,利用onchange事件或者oninput事件,因为传过来了id,所以利用id再对当前矩形进行操作就行,部分代码如下所示:

 // 内容变换
                contentChange(e) {
                    let id = '#' + this.rightstyle.currentId;
                    if (e !== undefined) {
                        let content = e.target.value
                            // 赋值给当前矩形框
                        $(id).text(content)
                    }
                },
                // 矩形宽度修改
                widthChange(e) {
                    let id = '#' + this.rightstyle.currentId;
                    let width = e.target.value + 'px';
                    $(id).parent().css('width', width);
                },
                // 矩形高度修改
                heightChange(e) {
                    let id = '#' + this.rightstyle.currentId;
                    let height = e.target.value + 'px';
                    $(id).parent().css('height', height);
                },
                // 行高
                lineHeightChange(e) {
                    let id = '#' + this.rightstyle.currentId;
                    let lineHeight = e.target.value + 'px';
                    $(id).parent().css('line-height', lineHeight);
                },
                // 矩形内字体大小修改
                fontSizeChange(e) {
                    let id = '#' + this.rightstyle.currentId;
                    let fontSize = e.target.value + 'px';
                    $(id).css('font-size', fontSize);
                },
                // 矩形内字体粗细修改
                fontWeightChange(e) {
                    let id = '#' + this.rightstyle.currentId;
                    let fontWeight = e.target.value;
                    $(id).css('font-weight', fontWeight);

                },
                // 矩形内字体颜色修改
                fontColorChange(e) {
                    let id = '#' + this.rightstyle.currentId;
                    let fontColor = e.target.value;

                    $(id).css('color', fontColor);
                },
                // 矩形背景颜色修改
                backgroundColorChange(e) {
                    let id = '#' + this.rightstyle.currentId;
                    let backgroundColor = e.target.value;
                    $(id).css('background-color', backgroundColor);
                },
                // 对齐方式修改
                alignClick(e) {
                    let id = '#' + this.rightstyle.currentId;
                    let alignment = e.target.getAttribute('name');
                    $(id).parent().css('text-align', alignment);
                },
                // 删除矩形
                deleteItem() {
                    let id = '#' + this.rightstyle.currentId;
                    let res = confirm('确定要删除吗?')
                    if (res) {
                        $(id).parent().remove();
                        $('.rectangleAttrcpn').hide();
                        $('.imageAttrcpn').hide();
                        $('.videoAttrcpn').hide();
                        $('.attrModuleNullcpn').show();

                    }

                }

这样就实现了属性设置功能,图片和视频的也是类似的。

六、天气和时间

时间比较简单,利用new Date()结合时间戳的道理来实现,天气我是调的一个别人的接口,因为有次数限制,所以这里网站预览的我是取消的天气api调用的。

七、预览

预览因为我没有很好的解决方案,这里就利用了css3的一些动画属性来产生预览的效果,点击预览的时候就把画布以外的元素隐藏了,并且取消元素的事件,防止它们在画布中触发事件。

总结:

最后,因为我还是前端菜鸟,如果有不完善的地方希望大家指出来,这个功能暂时还没开源,如果后续有需要可以开源给大家使用。大家也可以加我wx:meteor20150213进行交流