前言
前段时间做运营活动搭建平台,其中一个主要功能:编辑页面分为左侧-组件区与右侧-预览区,需要实现组件区的内容可自由放置到预览区内。
类似下图所示:
社区内有一些类似的功能实现,但使用的方式大同小异,都离不开拖拽能力。我们日常开发中会经常用到的拖拽,如拖拽排序,拖拽上传等。当然拖拽的 npm 包也有很多,比较好用的包有 react-dnd
, vue
自带的拖拽能力等。
但我们的预览区采用的是 iframe 方式,社区好用的类库一般不支持跨 iframe 的拖拽的能力。此处我们选择了使用原生拖拽 drag 和 dropAPI
需要实现的主要功能,有两点:
1、检测拖动到 iframe 内部和外部。
2、数据驱动来进行 iframe 内部组件的展示。
我们简单生成页面的功能:
-
//搭建编辑页
-
//drag.jsx
-
import React, { useState, useEffect } from
'react';
-
import Drag from
'./drag.js';
-
-
require(
'./styles.less');
-
-
//iframe hooks
-
const useIframeLoad = () => {
-
const [iframeState, setIframeState] = useState(
false);
-
const [windowState, setWindowState] = useState( document.readyState ===
"complete");
-
-
const iframeLoad = () => {
-
const iframeEle = document.getElementById(
"my-iframe");
-
iframeEle && setIframeState(iframeEle.contentDocument.readyState ===
"complete");
-
if (!iframeState && iframeEle) {
-
iframeEle.onload = () => {
-
setIframeState(
true);
-
};
-
}
-
};
-
useEffect(() => {
-
if (!windowState) {
-
setIframeState(
false);
-
window.addEventListener(
'load', () => {
-
setWindowState(
true);
-
iframeLoad();
-
})
-
}
else {
-
iframeLoad();
-
}
-
}, []);
-
return iframeState;
-
}
-
-
export
default () => {
-
-
const init = () => {
-
Drag.init({
-
dragEle: document.getElementById(
'drag-box'),
-
dropEle: document.getElementById(
'my-iframe').contentDocument.getElementById(
'drop-box')
-
})
-
}
-
-
useIframeLoad() && init();
-
-
return <>
-
<!-- 组件区 -->
-
<div id=
"drag-box">
-
<div className=
"drag-item">拖动元素</div>
-
<div className=
"drag-item">拖动元素</div>
-
<div className=
"drag-item">拖动元素</div>
-
</div>
-
<!-- 预览区 -->
-
<div className=
"drop-content">
-
<iframe id=
"my-iframe" src=
"#/iframe" style={{ width:
"100%", height:
"480px", border:
"none" }}/>
-
</div>
-
</>
-
}
预览区 iframe 页:
-
//iframe.jsx
-
import React from
'react';
-
-
require(
'./styles.less');
-
-
export
default () => {
-
return <div id=
"drop-box">
-
<div className=
"item">元素
1</div>
-
<div className=
"item">元素
2</div>
-
<div className=
"item">元素
3</div>
-
</div>
-
}
此时,简单的搭建编辑布局已完成。接下来,我们看下拖拽部分:
跨 iframe 拖拽
首先我们可以看下有哪些原生事件
原生事件
-
drag
// 拖动元素或文本选择时将触发此事件 (相当于拖动过程中,一直触发此事件)
-
dragstart
//当用户开始拖动一个元素或者一个选择文本的时候 ,将触发此事件
-
dragend
//当拖动操作结束时(通过释放鼠标按钮或按退出键),将触发此事件
-
-
dragover
//当被拖动元素在释放区内移动时,将触发此事件
-
dragenter
//被拖动元素进入到释放区所占据得屏幕空间时,将触发此事件
-
dragleave
//当被拖动元素没有放下就离开释放区时,将触发此事件
-
dragexit
//当元素不再是拖动操作的立即选择目标时,将触发此事件
-
drop
//当被拖动元素在释放区里放下时,将触发此事件
原生 drag 和 drop 拖拽
基于需求,拆分出拖拽的关键流程:
初始化元素 设置拖动元素和目标节点
注册事件 对拖动元素和目标节点元素注册 drag 事件
监听事件 拖动过程中生成占位节点,拖动结束删除此占位节点
不完全代码如下:
-
//drag.js
-
class Drag {
-
params = {}
-
-
init = (params) => {
-
....
-
};
-
-
//初始化设置拖动元素
-
initDrag = dragEle => {
-
if(dragEle.childNodes.length) {
-
const { length } = dragEle.childNodes;
-
let i =
0
-
while (i< length) {
-
this.setDrag(dragEle.childNodes[i]);
-
i +=
1;
-
}
-
}
else {
-
this.setDrag(dragEle);
-
}
-
}
-
-
//初始化释放区
-
initDrop = dropEle => {
-
if (dropEle.childNodes.length) {
-
const { length } = dropEle.childNodes;
-
let i =
0;
-
while (i < length) {
-
this.setDrop(dropEle.childNodes[i]);
-
i +=
1;
-
}
-
}
else {
-
this.setDrop(dropEle);
-
}
-
}
-
-
//拖动元素注册事件
-
setDrag = el => {
-
el.setAttribute(
"draggable",
"true");
-
el.ondragstart = this.dragStartEvent;
-
el.ondrag = this.dragEvent;
-
el.ondragend = this.dragEndEvent;
-
};
-
-
//释放区注册事件
-
setDrop = el => {
-
el.ondrop = this.dropEvent;
-
el.ondragenter = this.dragEnterEvent;
-
el.ondragover = this.dragOverEvent;
-
el.ondragleave = this.dragLeaveEvent;
-
}
-
......
-
-
//创建占位元素
-
createElePlaceholder = (() => {
-
let ele = null;
-
return () => {
-
if (!ele) {
-
ele = document.createElement(
"div");
-
ele.setAttribute(
"id",
"drag-ele-placeholder");
-
ele.innerHTML =
`<div style="width: 100%; height:50px; position: relative">
-
<div style="width: 150px; height: 40px; text-align: center; position: absolute;
-
left: 0; right: 0; top: 0; bottom:0; margin: auto; background: #878; line-height: 40px">放置组件</div>
-
</div>`;
-
}
-
return ele;
-
};
-
})();
-
-
//移除占位元素
-
removePlaceholderEle = () => {
-
const iframe = this.getIframe();
-
const removeEle = iframe.contentDocument.getElementById(
"drag-ele-placeholder");
-
const { dropEle } = this.params;
-
if(this.isHasPlaceholderEle()) { dropEle.removeChild(removeEle) };
-
}
-
-
/****** 事件处理 ******/
-
dragEndEvent = ev => {
-
this.removePlaceholderEle()
-
console.log(
'拖拽结束');
-
console.log(
'删除占位元素');
-
};
-
//插入占位元素
-
dragEnterEvent = ev => {
-
ev.preventDefault();
-
const insertEle = this.createElePlaceholder();
-
ev.target.before(insertEle);
-
console.log(
'进入到可放置区');
-
console.log(
'插入占位元素');
-
};
-
//删除占位元素
-
dragLeaveEvent = ev => {
-
ev.preventDefault();
-
this.removePlaceholderEle()
-
console.log(
'离开放置区');
-
console.log(
'删除占位元素');
-
};
-
-
dropEvent = ev => {
-
ev.preventDefault();
-
console.log(
'在放置区放开鼠标');
-
}
-
}
-
-
export
default
new Drag();
初步完成后,效果如下:
此处存在一些问题:
在插入时,页面闪烁
只有鼠标位置进入释放区,才触发进入事件
无法实现第一个元素的添加
问题分析
当拖到预览区时,会触发预览区内的节点 dragenter 事件。每当在当前节点上插入占位元素时,此节点的位置会发生变化,触发节点 dragleave 事件,同时删除占位元素。此过程一直重复,导致一直闪烁。
上述 2,3 问题,是由于 drag/drop 本身 api 限制
由于现在的方式无法真正完美的实现功能,决定弃用 dragover,dragenter,dragleave 事件
重新梳理需要优化的功能点:
当拖动元素和 iframe 的边有接触的时候,就代表进入释放区
拖动可以实现元素上面插入,和元素下面插入
使用坐标精准计算,来处理进入释放区和在元素上面和下面插入
对 drag.js 做些改造:
-
class Drag {
-
params = {}
-
-
// 声明
-
mouseOffsetBottom =
0;
-
mouseOffsetRight =
0;
-
-
init = (params) => {
-
...
-
};
-
//初始化设置拖动元素
-
initDrag = dragEle => {
-
....
-
}
-
//初始化释放区
-
initDrop = dropEle => {
-
...
-
}
-
//拖动元素注册事件
-
setDrag = el => {
-
...
-
};
-
//释放区注册事件
-
setDrop = el => {
-
...
-
}
-
-
//获取iframe的位置
-
getIframeOffset = () => {
-
const iframeEle = this.getIframe();
-
return iframeEle
-
? this.getRealOffset(iframeEle)
-
: { offsetLeft:
0, offsetTop:
0 };
-
};
-
-
//递归计算元素距离父元素的offset
-
getRealOffset = (el, parentName) => {
-
let left = el.offsetLeft;
-
let top = el.offsetTop;
-
if (el.offsetParent && el.offsetParent.tagName !== parentName) {
-
const p = this.getRealOffset(el.offsetParent, parentName);
-
left += p.offsetLeft;
-
top += p.offsetTop;
-
}
-
return { offsetLeft: left, offsetTop: top };
-
}
-
-
//获取元素位置
-
getElOffset = el => {
-
const { offsetTop: iframeTop } = this.getIframeOffset();
-
const { offsetTop: targetOffsetTop } = this.getRealOffset(el);
-
return {
-
midLine: el.clientHeight /
2 + targetOffsetTop + iframeTop,
-
topLine: targetOffsetTop + iframeTop,
-
bottomLine: el.clientHeight + targetOffsetTop + iframeTop
-
};
-
};
-
-
//释放区内部元素位置
-
getDropOffset = () => {
-
const result = [];
-
const { dropEle } = this.params;
-
const el = dropEle.childNodes;
-
-
let i =
0;
-
while (i < el.length) {
-
const midLine = this.getElOffset(el[i]);
-
result.push(midLine);
-
i +=
1;
-
}
-
return result;
-
};
-
-
//位置比较
-
locationCompare = (ev) => {
-
let inside =
false;
-
const { dropEle } = this.params;
-
console.log(ev.clientX);
-
// 拖动元素的位置
-
const sourceRight = ev.clientX + this.mouseOffsetRight;
-
const sourceLeft = sourceRight - ev.currentTarget.clientWidth;
-
-
const { offsetLeft: iframeLeft } = this.getIframeOffset();
-
const { offsetLeft: targetLeft } = this.getRealOffset(dropEle);
-
-
/*释放区的位置*/
-
const targetOffsetLeft = iframeLeft + targetLeft;
-
const targetOffsetRight = targetOffsetLeft + dropEle.clientWidth;
-
-
if (sourceRight > targetOffsetLeft && sourceLeft < targetOffsetRight) {
-
//拖动到释放区
-
inside =
true;
-
}
else {
-
//释放区外面
-
inside =
false;
-
}
-
return inside;
-
-
}
-
-
//插入占位元素
-
insertPlaceholderEle = (sourceMidLine) => {
-
const dropOffset = this.getDropOffset();
//释放区的位置属性
-
const insertEl = this.createElePlaceholder();
-
const { dropEle } = this.params;
-
const dropEleChild = dropEle.childNodes;
-
if (dropOffset.length) {
-
dropOffset.
map((item, i) => {
-
const Ele = dropEleChild[i];
-
//在元素前面插入占位元素
-
if (sourceMidLine > item.topLine && sourceMidLine < item.midLine) {
-
Ele.before(insertEl);
-
}
-
//在元素后面插入占位元素
-
if (sourceMidLine < item.bottomLine && sourceMidLine > item.midLine) {
-
this.index = i +
1;
-
Ele.after(insertEl);
-
}
-
//追加一个占位元素
-
if (sourceMidLine > dropOffset[dropOffset.length -
1].bottomLine) {
-
dropEle.
append(insertEl);
-
}
-
return item;
-
});
-
}
-
//插入第一个占位元素(当iframe内部没有组件)
-
if (!dropEleChild.length) {
-
dropEle.
append(insertEl);
-
}
-
}
-
-
/****** 事件处理 ******/
-
dragStartEvent = ev => {
-
// console.log('开始拖拽');
-
//获得鼠标距离拖拽元素的下边的距离
-
this.mouseOffsetBottom = ev.currentTarget.clientHeight - ev.offsetY;
-
//获得鼠标距离拖拽元素的右边的距离
-
this.mouseOffsetRight = ev.currentTarget.clientWidth - ev.offsetX;
-
};
-
-
dragEvent = ev => {
-
//获取拖拽元素中线距离屏幕上方的距离
-
const sourceMidLine =
-
ev.clientY + this.mouseOffsetBottom - ev.currentTarget.clientHeight /
2;
-
if(this.locationCompare(ev)) {
-
this.insertPlaceholderEle(sourceMidLine)
-
console.log(
'释放区内部')
-
}
else {
-
this.removePlaceholderEle()
-
console.log(
'释放区外面')
-
}
-
};
-
}
-
-
export
default
new Drag();
生成结果如下:
此时已经解决了不停闪烁的问题,以及精准坐标计算,实现元素的上下插入。
但是还是存在一些问题:
演示图中可以明显看到,拖动元素右边刚进入 iframe 的时候,可以插入占位元素,但是等到鼠标位置进入 iframe 的时候,就会又删除了元素
这是什么原因呢?
我们看一下打印的鼠标的坐标,可以看到鼠标位置进入 iframe 的时候,ev.clientX
突变成 0,由此可见,鼠标坐标进入 iframe 的时候,就以 iframe 为窗口了。导致鼠标的位置突变成 0,就导致计算位置出现偏差,从而拖拽元素被认为不在释放区内,所以就删除了占位元素。
怎么解决这个问题呢?
想到了几个方案:
一个是监听坐标的突变情况,然后重新计算位置,进一步进行比较位置。
把 iframe 放大和屏幕大于等于屏幕的大小,从拖动开始就使得在 iframe 里面。
方案分析:
第一个方案,监听坐标突变为 0 这个临界条件不靠谱,因为每隔 50ms 拖动事件才触发,根据你移动鼠标的快慢,每次鼠标进入 iframe 获取的 clientX 不一致,第一种方案不可行。
第二个方案,iframe 放大,理论上是可以的,我们来试试。主要是改变布局。
代码如下:
-
.drop-content {
-
position: absolute;
-
width:
100vw;
//iframe放大和窗口一般大
-
height:
100%;
-
}
-
-
#drop-box {
-
width:
375px;
//iframe内部元素设置宽度
-
margin:
100px auto;
-
-
.item {
-
...
-
}
-
}
演示效果如下演示可以看到,覆盖了左边的组件区。这是由于右边视图区 z-index 比较高导致的。
优化方案
有两个方案
元素布局移动调换位置,让右边视图区 dom 元素放在组件区的前边。
更改 z-index,让右边视图区的 z-index 低一点
方案 1
核心代码
-
//drag.jsx
-
//调换两个元素的位置
-
<>
-
<div className=
"drop-content">
-
<iframe id=
"my-iframe" src=
"#/iframe" style={{ width:
"100%", height:
"480px", border:
"none" }}/>
-
</div>
-
<div id=
"drag-box">
-
<div className=
"drag-item">拖动元素</div>
-
<div className=
"drag-item">拖动元素</div>
-
<div className=
"drag-item">拖动元素</div>
-
</div>
-
</>
实现后的效果
可以看出来,完美解决了拖动的问题。但是就是对布局进行了改变。
方案 2
核心代码
-
.drop-content {
-
position: absolute;
-
z-index:
-1;
//让iframe的z-index低一点
-
width:
100vw;
//iframe放大和窗口一般大
-
height:
100%;
-
}
-
-
#drop-box {
-
width:
375px;
//iframe内部元素设置宽度
-
margin:
100px auto;
-
-
.item {
-
width:
100%;
-
height:
50px;
-
background-color: #
875;
-
}
-
}
实现后的效果
演示中可以看出来,拖拽的问题完美解决,但是 iframe 的里面元素点击事件没有触发。
想了想,既然 z-index 可以解决 clientX 的突变问题,那是不是可以不用放大 iframe 来做?这样也会不影响事件的触发,那我们试试吧。
核心代码
-
//drag.js
-
//开始拖拽
-
dragStartEvent = ev => {
-
document.getElementsByClassName(
"drop-content")[
0].style.zIndex =
-
"-1";
-
};
-
-
//拖拽结束
-
dragEndEvent = ev => {
-
ev.preventDefault();
-
document.getElementsByClassName(
"drop-content")[
0].style.zIndex =
"0";
-
};
-
演示效果如下
很好,这样也可以完美解决拖动的问题,而且不用改变 dom 的位置。
滚动处理
当视图区元素比较多,页面出现滚动条时,会不会出现问题呢?我们试着把 iframe 的高度写高一点
<iframe id="my-iframe" src="#/iframe" style={{ width: "100%", height: "880px", border: "none" }}/>
演示效果如下
演示中可以看出来,页面出现滚动条,视图区滚动上去,iframe 顶部滚入到屏幕顶部的时候,我们来拖动元素插入的时候,就会出现,错位插入,这是计算又出了问题?
仔细看看代码,iframe 顶部滚入到屏幕顶部的时候,就会出现计算出负数的情况,导致计算偏差,从而导致插入占位元素错位。
-
//递归计算元素距离父元素的offset
-
getRealOffset = (el, parentName) => {
-
let left = el.offsetLeft;
-
let top = el.offsetTop;
-
if (el.offsetParent && el.offsetParent.tagName !== parentName) {
-
const p = this.getRealOffset(el.offsetParent, parentName);
-
left += p.offsetLeft;
-
top += p.offsetTop;
-
}
-
return { offsetLeft: left, offsetTop: top };
-
}
优化计算方案
核心代码
-
//计算元素距离父元素的offset
-
getRealOffset = (el, parentName) => {
-
const { left, top } = el.getBoundingClientRect();
-
return { offsetLeft: left, offsetTop: top };
-
}
使用 getBoundingClientRect 这个方法获得具体窗口的位置
演示如下本次优化,可以很完美的解决了拖动的一些问题,以上两种方案都是行的。
跨 iframe 通信
如何在拖动元素插入之后,让 iframe 内部的数据也实时更新渲染呢?
思路如下:
iframe 内挂载一个 update 方法
在拖动完成后的回调里面,调用 update,传入数据
触发 iframe 内部元素的渲染
维护一个组件的数据 store,getStore,和 setStore方法
-
//store.js
-
class Store {
-
state = {
-
list: []
-
}
-
getStore = () => this.state
-
setStore = (data) => {
-
this.state = { ...this.state, ...data }
-
}
-
}
-
-
export
default
new Store()
组件的插入对应数据的处理,包含,add 和 insert操作,以及同步更新 iframe的方法
-
// update.js
-
import Store from
'./store';
-
-
const add = (params) => {
-
const { list } = Store.getStore()
-
Store.setStore({ list: [...list, params.data]})
-
};
-
-
const insert = (params) => {
-
const { list } = Store.getStore()
-
const { index } = params;
-
list.splice(index,
0, params.data)
-
Store.setStore({ list: [...list] })
-
};
-
-
const update = {
-
add,
-
insert
-
}
-
-
//更新iframe内部数据方法
-
const iframeUpdate = (params) => {
-
document.getElementById(
"my-iframe") &&
-
document.getElementById(
"my-iframe").contentWindow &&
-
document.getElementById(
"my-iframe").contentWindow.update &&
-
document.getElementById(
"my-iframe").contentWindow.update(params);
-
}
-
-
export
default (params) => {
-
const {
type, ...argv } = params;
-
if(!
type)
return Promise.reject()
-
return
new Promise(r => r())
-
.then(() => update[
type](argv))
-
.then(() => {
-
const { list } = Store.getStore()
-
iframeUpdate(list)
-
})
-
}
拖动的时候,拖动完毕后,将元素的操作类型,以及要插入的元素的位置,通过回调函数传递出去
-
//drag.js
-
class Drag {
-
params = {}
-
-
mouseOffsetBottom =
0;
-
mouseOffsetRight =
0;
-
-
index =
0;
//插入元素的下标
-
type =
'add';
//操作类型
-
-
init = (params) => {
-
...
-
};
-
-
...
-
-
//计算元素距离父元素的offset
-
getRealOffset = (el, parentName) => {
-
const { left, top } = el.getBoundingClientRect();
-
return { offsetLeft: left, offsetTop: top };
-
}
-
-
//获取元素位置
-
getElOffset = el => {
-
const { offsetTop: iframeTop } = this.getIframeOffset();
-
const { offsetTop: targetOffsetTop } = this.getRealOffset(el);
-
return {
-
midLine: el.clientHeight /
2 + targetOffsetTop + iframeTop,
-
topLine: targetOffsetTop + iframeTop,
-
bottomLine: el.clientHeight + targetOffsetTop + iframeTop
-
};
-
};
-
-
//释放区内部元素位置
-
getDropOffset = () => {
-
const result = [];
-
const { dropEle } = this.params;
-
const el = dropEle.childNodes;
-
-
let i =
0;
-
while (i < el.length) {
-
const midLine = this.getElOffset(el[i]);
-
result.push(midLine);
-
i +=
1;
-
}
-
return result;
-
};
-
-
...
-
-
//插入占位元素
-
insertPlaceholderEle = (sourceMidLine) => {
-
const dropOffset = this.getDropOffset();
//释放区的位置属性
-
const insertEl = this.createElePlaceholder();
-
const { dropEle } = this.params;
-
const dropEleChild = dropEle.childNodes;
-
if (dropOffset.length) {
-
dropOffset.
map((item, i) => {
-
const Ele = dropEleChild[i];
-
//在元素前面插入占位元素
-
if (sourceMidLine > item.topLine && sourceMidLine < item.midLine) {
-
Ele.before(insertEl);
-
this.index = i;
-
this.
type =
'insert'
-
}
-
//在元素后面插入占位元素
-
if (sourceMidLine < item.bottomLine && sourceMidLine > item.midLine) {
-
this.index = i +
1;
-
Ele.after(insertEl);
-
this.
type =
'insert'
-
}
-
//追加一个占位元素
-
if (sourceMidLine > dropOffset[dropOffset.length -
1].bottomLine) {
-
dropEle.
append(insertEl);
-
this.
type =
'add'
-
}
-
return item;
-
});
-
}
-
//插入第一个占位元素(当iframe内部没有组件)
-
if (!dropEleChild.length) {
-
this.
type =
'add'
-
dropEle.
append(insertEl);
-
}
-
}
-
-
/****** 事件处理 ******/
-
//开始拖拽
-
dragStartEvent = ev => {
-
document.getElementsByClassName(
"drop-content")[
0].style.zIndex =
-
"-1";
-
//获得鼠标距离拖拽元素的下边的距离
-
this.mouseOffsetBottom = ev.currentTarget.clientHeight - ev.offsetY;
-
//获得鼠标距离拖拽元素的右边的距离
-
this.mouseOffsetRight = ev.currentTarget.clientWidth - ev.offsetX;
-
};
-
-
dragEvent = ev => {
-
//获取拖拽元素中线距离屏幕上方的距离
-
const sourceMidLine =
-
ev.clientY + this.mouseOffsetBottom - ev.currentTarget.clientHeight /
2;
-
if(this.locationCompare(ev)) {
-
this.insertPlaceholderEle(sourceMidLine)
-
// console.log('释放区内部')
-
}
else {
-
this.removePlaceholderEle()
-
// console.log('释放区外面')
-
}
-
};
-
-
//拖拽结束
-
dragEndEvent = ev => {
-
ev.preventDefault();
-
document.getElementsByClassName(
"drop-content")[
0].style.zIndex =
"0";
-
const { callback } = this.params;
-
this.locationCompare(ev) &&
-
callback &&
-
callback({
-
type: this.
type,
-
index: this.index
-
});
-
};
-
}
-
-
export
default
new Drag();
在拖动完毕后调用 update,更新数据源
-
//drag.jsx
-
import React, { useState, useEffect } from
'react';
-
import Drag from
'./drag';
-
import update from
'@/store/update';
-
-
require(
'./styles.less');
-
-
//iframe hooks
-
const useIframeLoad = () => {
-
...
-
//iframe加载状态的hooks
-
return iframeState;
-
}
-
-
export
default () => {
-
-
const callback = params => {
-
update({ ...params, data: { name:
new Date().getTime() } })
-
}
-
-
const init = () => {
-
Drag.init({
-
dragEle: document.getElementById(
'drag-box'),
-
dropEle: document.getElementById(
'my-iframe').contentDocument.getElementById(
'drop-box'),
-
callback
-
})
-
}
-
-
useIframeLoad() && init();
-
return <>
-
...
-
</>
-
}
iframe 内部 update 方法被调用,就会触发数据更新和组件的渲染。
-
//iframe.jsx
-
import React, { useState } from
'react';
-
-
require(
'./styles.less');
-
-
export
default () => {
-
const [list, setList] = useState([]);
-
-
//挂载update方法,跨iframe数据传递,更新
-
window.update = params => {
-
setList(params);
-
}
-
-
return <div id=
"drop-box">
-
{
-
list.
map((item) =>
-
<div className=
"item" key={item.name} onClick={() => alert(
'点击事件')}>元素{item.name}</div>
-
)
-
}
-
</div>
-
}
演示效果如下
最终实现了跨 iframe 的拖拽及通信。
总结
此次运营页搭建拖拽通信功能,是在不断打怪升级中完成。其中涉及到以下几个点:
元素进入视图区的判断 iframe 的左边距离屏幕的 x 的坐标 < 被拖元素的右边距离屏幕的 x 的坐标 < iframe 的右边距离屏幕的 x 的坐标。
元素上下插入 被拖元素的中线距离屏幕的 y 的坐标 < iframe 内部元素中线距离屏幕的 y 的坐标 属于前面插入,被拖元素的中线距离屏幕的 y 的坐标 > iframe 内部元素中线距离屏幕的 y 的坐标 属于后面插入。
clientX 坐标突变的问题 z-index 解决处理。
滚动位置问题 getBoundingClientRect 解决。
希望本篇文章对你有所帮助,欢迎大家一起交流分享呀。
每天进步一点点,质变从关注 大转转 FE 开始。
转载:https://blog.csdn.net/P6P7qsW6ua47A2Sb/article/details/114297621