<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
低程式碼引擎是低程式碼分層架構中最複雜的部分,引擎的核心功能包含入料、設計、畫布渲染和出碼等,它們的含義如下:
本文主要介紹拖拽定位,即:拖拽過程中探測元件的可插入點。為了給渲染器提供一個純淨的渲染環境,渲染器和設計器處於不同的 iframe 中,因此拖拽元件,不僅涉及在同一個 iframe 中拖拽元件,還涉及跨 iframe 拖拽元件。渲染器所在的 iframe 由設計器喚起,在正式介紹拖拽定位之前,先介紹如何喚起渲染器 iframe。
iframe 元素的常見用法是將它的 src 屬性設定成一個固定的網頁地址,讓它在當前網頁嵌入另一個已經存在的網頁,但渲染器沒有固定的網頁地址,所以在這裡要使用一種不常見的用法,即呼叫 document.write 方法給 iframe 所在的檔案寫入它要載入的內容。設計器喚起渲染器 iframe 的流程如下圖所示:
設計器環境和渲染器環境通過 host 相互通訊,SimulatorRenderer 給 host 提供了一些 API 幫助設計器完成互動,設計器給 host 提供了一些 API 幫助渲染器完成畫布渲染。
在設計器環境中與渲染器環境相關的只是一個 iframe 元素,如下:
<iframe name="SimulatorRenderer" className="vitis-simulator-frame" style={frameStyle} ref={this.mountContentFrame} />
往 iframe 寫入內容發生在 host.mountContentFrame 方法中,程式碼片段如下:
this.frameDocument!.open() this.frameDocument!.write( `<!doctype html> <html class="engine-design-mode"> <head> <meta charset="utf-8"/> // 這裡是渲染器環境要載入的css樣式指令碼 ${styleTags} </head> <body> // 這裡是渲染器環境要載入的js指令碼 ${scriptTags} </body> </html>` ) this.frameDocument!.close() // 監聽iframe載入成功和載入失敗的事件 this.frameWindow!.addEventListener('load', loaded); this.frameWindow!.addEventListener('error', errored);
用低程式碼引擎設計介面時,為了讓渲染器環境能成功的顯示畫布,上述 scriptTags 中至少包含 react、react-dom 和 vitis-lowcode-simulator-renderer 的js 指令碼,在開發階段 vitis-lowcode-simulator-renderer 的 js 指令碼地址是 http://localhost:5555/js/simulator-renderer.js,等釋出之後vitis-lowcode-simulator-renderer 的 js 指令碼地址是其 npm 包的 js 地址。
拖拽定位指的是當元件在畫布區域拖動時,介面實時的顯示元件最近的可放置位置,這是一個與設計器強相關的功能,所以與設計器處於同一個 iframe,相關的 DOM 元素被疊放在畫布區域的上面,如下圖所示:
上圖藍線所在的位置就是被拖動元件最近可放置的位置,實現該功能需用到 Element.getBoundingClientRect() 方法和 HTML5 的拖放事件。給渲染器中的低程式碼元件設定 ref 屬性,當其裝載到介面上即可得到元件的 DOM 元素,從而計算出拖拽過程中滑鼠經過的低程式碼元件。
低程式碼元件的拖拽能力由 Dragon 範例提供,與拖拽相關的概念有如下3個:
DragObject 是一個聯合型別,拖拽不同位置的低程式碼元件,它的型別有所不同,其介面型別定義如下:
interface DragNodeObject { type: DragObjectType.Node; // 被拖拽的是畫布中的低程式碼元件 node: Node; } interface DragNodeDataObject { type: DragObjectType.NodeData; // 被拖拽的是元件面板上的低程式碼元件 data: ComponentSpec; } type DragObject = DragNodeObject | DragNodeDataObject
設計器用 LocationEvent 來計算被拖拽物件最近的可放置點,其介面型別定義如下:
interface LocationEvent { dragObject: DragObject, originalEvent: DragEvent, clientX: number, clientY: number }
上述介面中 clientY 和 clientX 來自於 DragEvent 物件,它們用來計算畫布中離滑鼠最近的 Node。
DropLocation是拖拽操作要計算的結果,介面型別定義如下:
interface DropLocation { // 被拖拽物件可放置的容器 containerNode: Node; // 被拖拽物件在容器中的插入點 index: number; }
以拖拽元件面板中的低程式碼元件為例,在畫布區域顯示元件最近的可放置點,總體而言,需經歷6個步驟。
iframe 和元件面板中的低程式碼元件繫結拖放事件,得到 DragObject,程式碼片段如下:
// 當元件面板中的元件開始拖動時 <div draggable={true} onDragStart={() => onDragStart(item.packageName)}>xxx</div> const onDragStart = (packageName: string) => { // 得到DragObject dragon.onNodeDataDragStart(packageName) } // 給 iframe 繫結dragover事件,當拖動操作進入畫布區域時觸發事件 this.frameDocument?.addEventListener('dragover', (e: DragEvent) => { e.preventDefault() this.project.designer.dragon.onDragOver(e) })
LocationEvent 將在 iframe 的 dragover 事件處理程式中實時獲取,程式碼如下:
onDragOver = (e: DragEvent) => { // 獲取 locateEvent 只是簡單的取值 const locateEvent = this.createLocationEvent(e) } createLocationEvent = (e: DragEvent): LocationEvent => { return { dragObject: this.dragObject, originalEvent: e, clientX: e.clientX, clientY: e.clientY } }
Node 被裝載在渲染器環境中,只有 SimulatorRenderer 範例才知道每個 Node 的位置,因此這一步需要呼叫 SimulatorRenderer 給 host 提供的getClosestNodeIdByLocation 方法,getClosestNodeIdByLocation 的程式碼如下:
getClosestNodeIdByLocation = (point: Point): string | undefined => { // 第一步:找出包含 point 的全部 dom 節點 const suitableContainer = new Map<string, DomNode>() for (const [id, domNode] of reactInstanceCollector.domNodeMap) { const rect = this.getNodeRect(id) if (!domNode || !rect) continue const { width, height, left, top } = rect if (left < point.clientX && top < point.clientY && width + left > point.clientX && height + top > point.clientY) { suitableContainer.set(id, domNode) } } // 第二步:找出離 point 最近的 dom 節點 const minGap: {id: string| undefined; minArea: number} = { id: undefined, minArea: Infinity } for (const [id, domNode] of suitableContainer) { const { width, height } = domNode.rect if (width * height < minGap.minArea) { minGap.id = id; minGap.minArea = width * height } } return minGap.id }
上述 reactInstanceCollector 物件中儲存了畫布上全部低程式碼元件的 DOM 節點,實現這個目的需藉助 React 的 ref 屬性,在這裡不展開介紹。
每個低程式碼元件都能設定巢狀規則,規定哪些元件能做為它的子元素和父元素,不符合規則的元件則不可放置,在這一步將使用元件的巢狀規則,程式碼如下:
getDropContainer = (locateEvent: LocationEvent) => { // 從上一步得來的潛在容器 let containerNode = this.host.getClosestNodeByLocation({clientX: locateEvent.clientX, clientY: locateEvent.clientY}) const thisComponentSpec: ComponentSpec = locateEvent.dragObject.data while(containerNode) { if (containerNode.componentSpec.isCanInclude(thisComponentSpec)) { return containerNode } else { // 繼續往上找父級 containerNode = containerNode.parent } } }
容器可能包含多個子元素,在這一步將利用滑鼠位置計算被拖動的物件在容器中的插入點,得到最終的 DropLocation ,程式碼如下:
// 初始值 const dropLocation: DropLocation = { index: 0, containerNode: container} const { childrenSize, lastChild } = container const { clientY } = locateEvent if (lastChild) { const lastChildRect = this.designer.getNodeRect(lastChild.id) // 判斷是否要插到容器的末尾 if (lastChildRect && clientY > lastChildRect.bottom) { dropLocation.index = childrenSize } else { let minDistance = Infinity // 容器中最近的插入點 let minIndex = 0 for (let index = 0 ; index < childrenSize; index ++) { const child = container.getChildAtIndex(index)! const rect = this.designer.getNodeRect(child.id) if (rect && Math.abs(rect.top - clientY) < minDistance) { minDistance = Math.abs(rect.top - clientY) minIndex = index } } dropLocation.index = minIndex } } return dropLocation
經過前面的步驟已經得到了插入位置,現在需要在介面上給使用者顯示相應的提示,這裡要用到狀態管理庫 MobX,在此之前需將 Dragon 範例變成一個可觀察物件,再在React元件中使用 mobx-react 匯出的 observer,程式碼如下:
import { observer } from 'mobx-react' observer(function InsertionView() { const [style, setStyle] = useState<React.CSSProperties>({}) useEffect(() => { const dropLocation = observableProject.designer.dragon.dropLocation if (!dropLocation) { setStyle({}) } else { const { width, left, top } = dropLocation.containerRect setStyle({ borderTopStyle: 'solid', width, left, top }) } }, [observableProject.designer.dragon.dropLocation]) return ( // 這個元素被絕對定位到畫布區域的上面 <div className='vitis-insertion-view' style={style}></div> ) })
當 dragon.dropLocation 的值發生變化時,InsertionView 元件將重新渲染,實時的給使用者提示拖拽物件對接的可插入點。
低程式碼的拖拽定位遠不止本文介紹的這些功能,至少還包含懸停探測,詳情可檢視開源專案。 該開源專案持續更新。
以上就是JS分層架構低程式碼跨iframe拖拽範例詳解的詳細內容,更多關於JS分層架構跨iframe拖拽的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45