久久亚洲成a人片熟女精品色一区二区三区|国产精品视频第一精品视频|av天堂热无码手机版|亚洲?v无码久久无遮挡|国产精品偷伦视频免费观看国产|麻豆国产自产精品丰满熟妇|av无码av不卡一区二区|久久亚洲精品中文字

ARTICLE DETAIL

資訊詳情

深耕商務(wù)建站與企業(yè)官網(wǎng)運(yùn)營(yíng)的一線實(shí)戰(zhàn)洞察。

前端圖編輯器核心揭秘:從數(shù)據(jù)模型到Canvas渲染的完整實(shí)戰(zhàn)

前端圖編輯器核心揭秘:從數(shù)據(jù)模型到Canvas渲染的完整實(shí)戰(zhàn) 先從結(jié)論說(shuō)起這類(lèi)名為“diagram-design”的項(xiàng)目核心目標(biāo)從來(lái)不是“畫(huà)出好看的圖”而是“讓圖和背后的數(shù)據(jù)關(guān)系對(duì)得上”。我在實(shí)際動(dòng)手做了幾個(gè)月之后最大的感受是圖表設(shè)計(jì)最難的部分不是渲染而是把數(shù)據(jù)模型、交互狀態(tài)和視覺(jué)表現(xiàn)三者解耦清楚。下面把我的完整設(shè)計(jì)與實(shí)操過(guò)程拆開(kāi)講從數(shù)據(jù)建模到渲染管線再到避坑實(shí)錄都整理出來(lái)。1. 整體設(shè)計(jì)思路先想清楚“圖”到底是什么1.1 核心需求拆解在動(dòng)工之前我先把這個(gè)標(biāo)題翻譯成具體的功能清單。所謂“diagram-design”往細(xì)了說(shuō)無(wú)非是三件事創(chuàng)建和編輯節(jié)點(diǎn)節(jié)點(diǎn)有坐標(biāo)、大小、樣式可以被拖拽、縮放、刪除、復(fù)制。建立連接關(guān)系節(jié)點(diǎn)之間通過(guò)邊連接邊能記錄從哪里出發(fā)、到達(dá)哪里還能在中途拐彎。整體視圖控制畫(huà)布可以縮放、平移局部細(xì)節(jié)和整體結(jié)構(gòu)可以切換查看。這三件事拆完之后我發(fā)現(xiàn)真正的難點(diǎn)在第一件事的前半句“坐標(biāo)、大小、樣式”這些數(shù)據(jù)從哪里來(lái)答案是從用戶的操作中來(lái)但操作只是表象背后必須有一份可以被持久化、被復(fù)制、被程序讀取的數(shù)據(jù)結(jié)構(gòu)。否則你畫(huà)出來(lái)的圖只能看不能存更不能參與業(yè)務(wù)邏輯判斷。我做了一個(gè)取舍所有的圖形元素都用純數(shù)據(jù)描述不把任何視覺(jué)狀態(tài)混進(jìn)去。節(jié)點(diǎn)的位置、顏色、大小是數(shù)據(jù)節(jié)點(diǎn)處于“選中”“高亮”這類(lèi)交互狀態(tài)則不屬于數(shù)據(jù)——它們是運(yùn)行時(shí)的臨時(shí)狀態(tài)。這個(gè)分離規(guī)則在后續(xù)開(kāi)發(fā)里幫我省了大量麻煩。1.2 為什么不自已從零折騰一套復(fù)雜引擎很多人一聽(tīng)到圖表設(shè)計(jì)第一反應(yīng)是去引入現(xiàn)成的開(kāi)源庫(kù)。這個(gè)思路沒(méi)有問(wèn)題但對(duì)于我這種希望深度定制交互、并且要把圖數(shù)據(jù)導(dǎo)入導(dǎo)出到其他系統(tǒng)的場(chǎng)景現(xiàn)成庫(kù)反而會(huì)帶來(lái)幾層限制它的數(shù)據(jù)格式是固定的你的業(yè)務(wù)模型得遷就它它的狀態(tài)管理是內(nèi)置的你想和外部框架聯(lián)動(dòng)得靠事件橋接它的自定義渲染能力再?gòu)?qiáng)也不可能完全覆蓋你想要的視覺(jué)規(guī)范。所以我的判斷是自研一個(gè)輕量級(jí)圖編輯器核心是值得的。這里“輕量級(jí)”三個(gè)字很重要它意味著不需要處理巨型圖、不需要復(fù)雜的動(dòng)畫(huà)引擎、不需要多用戶實(shí)時(shí)協(xié)同。我的目標(biāo)是支撐單畫(huà)布內(nèi)幾百個(gè)節(jié)點(diǎn)、上千條邊的規(guī)模同時(shí)保證交互不掉幀。這個(gè)量級(jí)下完全可以通過(guò)合理設(shè)計(jì)做到。當(dāng)然如果圖規(guī)模到了上萬(wàn)個(gè)節(jié)點(diǎn)或者需要多人同時(shí)編輯那自研的代價(jià)會(huì)急劇上升此時(shí)使用成熟的圖編輯平臺(tái)反而更劃算。取舍的關(guān)鍵指標(biāo)就一個(gè)圖和你的業(yè)務(wù)數(shù)據(jù)之間耦合得有多深。1.3 技術(shù)方案選型我的技術(shù)棧定在純前端主語(yǔ)言用 TypeScript。選擇 TypeScript 的理由非常直接圖數(shù)據(jù)涉及大量類(lèi)型定義節(jié)點(diǎn)類(lèi)型、邊類(lèi)型、端口類(lèi)型在編譯期就定死比靠人腦記憶字段名靠譜得多。數(shù)據(jù)渲染層我選擇了兩套渲染方案并存畫(huà)布Canvas負(fù)責(zé)主場(chǎng)景渲染SVG 負(fù)責(zé)少量裝飾性元素。具體理由在后面的渲染章節(jié)詳細(xì)展開(kāi)這里先給結(jié)論——Canvas 在復(fù)雜場(chǎng)景下性能優(yōu)勢(shì)明顯但文本適配和局部更新不如 SVG 靈活所以兩者各司其職。狀態(tài)管理方面我引入了一個(gè)簡(jiǎn)單的狀態(tài)機(jī)維護(hù)畫(huà)布當(dāng)前的模式空閑、拖拽節(jié)點(diǎn)中、連線中、框選中。每種模式對(duì)應(yīng)的鼠標(biāo)事件處理完全不同用狀態(tài)機(jī)可以避免大量互相打架的 if-else。整體架構(gòu)分成了三層數(shù)據(jù)層節(jié)點(diǎn)、邊、端口的純數(shù)據(jù)模型負(fù)責(zé)增刪改查和序列化。視圖層負(fù)責(zé)將數(shù)據(jù)模型渲染到畫(huà)布并處理視口變換。交互層監(jiān)聽(tīng)鼠標(biāo)、鍵盤(pán)事件將其翻譯成數(shù)據(jù)層的修改操作。這個(gè)分層的核心原則是單向數(shù)據(jù)流用戶操作 → 交互層解析 → 修改數(shù)據(jù)層 → 視圖層訂閱變化并重繪。交互層不直接改畫(huà)布視圖層不反向改數(shù)據(jù)數(shù)據(jù)層不感知任何 UI 細(xì)節(jié)。2. 核心數(shù)據(jù)模型把所有圖形變成可存儲(chǔ)的數(shù)據(jù)2.1 節(jié)點(diǎn)、邊、端口的建模細(xì)節(jié)先說(shuō)我經(jīng)過(guò)幾次重構(gòu)后的最終數(shù)據(jù)結(jié)構(gòu)。一張圖我用一個(gè)對(duì)象表示包含三塊interface Diagram { id: string; nodes: Mapstring, DiagramNode; edges: Mapstring, DiagramEdge; meta: DiagramMeta; } interface DiagramNode { id: string; type: string; x: number; y: number; width: number; height: number; ports: Port[]; // 連接錨點(diǎn) data: Recordstring, unknown; // 業(yè)務(wù)數(shù)據(jù)不渲染但持久化 } interface Port { id: string; position: top | bottom | left | right; offset: number; // 端口在該邊上的相對(duì)位置0~1 } interface DiagramEdge { id: string; sourceNodeId: string; sourcePortId: string; targetNodeId: string; targetPortId: string; points: Point[]; // 折線的中間拐點(diǎn)可為空 data: Recordstring, unknown; }我公開(kāi)過(guò)很多次這個(gè)數(shù)據(jù)結(jié)構(gòu)每次被問(wèn)到最多的都是為什么要單獨(dú)拆出一個(gè)Port概念而不是直接用節(jié)點(diǎn)坐標(biāo)計(jì)算連接點(diǎn)關(guān)鍵在于邊的連接位置必須是穩(wěn)定、可恢復(fù)的。如果一條邊連接的是“節(jié)點(diǎn)A的右側(cè)邊緣中間”那么無(wú)論節(jié)點(diǎn)A怎么移動(dòng)這條邊的連接點(diǎn)都會(huì)跟著右側(cè)邊緣跑。如果你只存了節(jié)點(diǎn)的中心點(diǎn)每次重新計(jì)算連接點(diǎn)就會(huì)受節(jié)點(diǎn)尺寸變化影響——節(jié)點(diǎn)一旦改變寬高整條邊的位置就會(huì)漂移。端口這個(gè)概念的另一個(gè)好處是它天然支持“同節(jié)點(diǎn)多連接錨點(diǎn)”。一個(gè)節(jié)點(diǎn)可能有多個(gè)輸出口每個(gè)輸出口都是獨(dú)立的端口邊只需要記錄端口 ID 即可不需要關(guān)心端口的具體坐標(biāo)。坐標(biāo)由視圖層根據(jù)節(jié)點(diǎn)位置實(shí)時(shí)計(jì)算。2.2 數(shù)據(jù)序列化與格式兼容圖數(shù)據(jù)最終要能被保存到文件、數(shù)據(jù)庫(kù)或者發(fā)送給后端。所以序列化格式的設(shè)計(jì)非常關(guān)鍵。我用的是帶版本號(hào)的 JSON Schema{ version: 1.3.0, diagram: { id: diagram-001, meta: { title: 訂單流程圖 }, nodes: [], edges: [] } }version字段是我踩過(guò)坑之后補(bǔ)上的。起初我在本地開(kāi)發(fā)時(shí)沒(méi)有版本號(hào)后來(lái)數(shù)據(jù)結(jié)構(gòu)調(diào)整舊數(shù)據(jù)全部無(wú)法解析幾千條測(cè)試數(shù)據(jù)直接作廢。加上版本號(hào)之后每次寫(xiě)解析器都先判斷版本再?zèng)Q定走哪條遷移分支數(shù)據(jù)兼容的問(wèn)題就徹底解決了。序列化時(shí)還應(yīng)遵循“可以丟了不致命但保留更好”的字段放最后的原則。核心節(jié)點(diǎn)和邊的 ID、連接關(guān)系是必須的缺了圖就廢了樣式和坐標(biāo)可以恢復(fù)成默認(rèn)值但保留可以提升還原度擴(kuò)展字段全部掛到data對(duì)象里不占主體結(jié)構(gòu)。2.3 撤銷(xiāo)與重做快照策略很關(guān)鍵撤銷(xiāo)和重做是圖編輯器的隱形能力沒(méi)有它任何誤操作都像毀尸現(xiàn)場(chǎng)。我的實(shí)現(xiàn)方式是每次數(shù)據(jù)變更之前記錄上一個(gè)完整快照變更完成后記錄新快照撤銷(xiāo)和重做就是在這兩個(gè)快照之間切換。問(wèn)題在于快照是深拷貝深拷貝的成本很高而很多操作只是一次小修改比如拖動(dòng)節(jié)點(diǎn)拖動(dòng)的過(guò)程中可能產(chǎn)生幾十次中間狀態(tài)。全量快照會(huì)讓內(nèi)存和 CPU 都吃緊。我最終采用了兩層策略拖拽、縮放這類(lèi)連續(xù)操作在開(kāi)始時(shí)存一份快照操作過(guò)程中產(chǎn)生的中間狀態(tài)不保存操作結(jié)束時(shí)再存一份最終快照。這樣一次拖拽永遠(yuǎn)只對(duì)應(yīng)一次撤銷(xiāo)步驟。其他離散操作添加節(jié)點(diǎn)、刪除邊、修改顏色每次操作前存一份快照。這個(gè)方案實(shí)現(xiàn)簡(jiǎn)單卻能把內(nèi)存占用降低一個(gè)數(shù)量級(jí)。我實(shí)測(cè)在 200 個(gè)節(jié)點(diǎn)的畫(huà)布上連續(xù)拖拽 10 秒撤銷(xiāo)棧里只新增了 2 個(gè)快照內(nèi)存波動(dòng)可以忽略。3. 渲染管線的核心細(xì)節(jié)畫(huà)布為什么比想象中復(fù)雜3.1 Canvas 和 SVG 的取舍在圖表設(shè)計(jì)這個(gè)領(lǐng)域Canvas 和 SVG 的爭(zhēng)論從來(lái)沒(méi)有定論但我的選擇標(biāo)準(zhǔn)非常實(shí)用誰(shuí)能在我的場(chǎng)景下用更少的代碼達(dá)到更好的交互體驗(yàn)誰(shuí)就上。SVG 的最大優(yōu)勢(shì)是 DOM 化每個(gè)圖形就是一個(gè) DOM 節(jié)點(diǎn)可以綁定事件、修改樣式、單獨(dú)更新、配 CSS 動(dòng)畫(huà)。對(duì)于節(jié)點(diǎn)數(shù)較少幾十到一百的圖表SVG 的開(kāi)發(fā)效率明顯更高因?yàn)楹芏噤秩炯?xì)節(jié)瀏覽器幫你處理了。但當(dāng)節(jié)點(diǎn)數(shù)上升到幾百邊數(shù)上千DOM 節(jié)點(diǎn)數(shù)量開(kāi)始拖慢頁(yè)面。任何一個(gè)節(jié)點(diǎn)的重繪都可能觸發(fā)瀏覽器的樣式重算和布局更新響應(yīng)延遲會(huì)明顯變大。Canvas 沒(méi)有這個(gè)負(fù)擔(dān)所有圖形都畫(huà)在一塊畫(huà)布上沒(méi)有 DOM 節(jié)點(diǎn)更新時(shí)只需調(diào)用重繪函數(shù)。代價(jià)是你需要自己實(shí)現(xiàn)命中檢測(cè)、事件映射、局部更新等于瀏覽器的 DOM 能力沒(méi)法用了都得自己寫(xiě)。我在項(xiàng)目中的最終策略是主場(chǎng)景用 Canvas 渲染全部節(jié)點(diǎn)和邊保證性能和渲染上限浮動(dòng)的連接預(yù)覽線、我正在拖拽中的節(jié)點(diǎn)輪廓提醒這類(lèi)“臨時(shí)裝飾”用 SVG 疊在畫(huà)布上層因?yàn)檫@類(lèi)元素只存在于交互瞬間量小而靈活SVG 的語(yǔ)法表達(dá)更舒服。3.2 視口變換一張圖兩個(gè)坐標(biāo)系圖編輯器必須支持縮放和平移這是硬需求。實(shí)現(xiàn)縮放平移的核心是理解兩套坐標(biāo)系之間的換算世界坐標(biāo)World Coordinates數(shù)據(jù)模型里的 x、y 是真實(shí)坐標(biāo)節(jié)點(diǎn)之間的距離關(guān)系用世界坐標(biāo)描述它不隨視口變化。屏幕坐標(biāo)Screen Coordinates畫(huà)布上用戶實(shí)際看到的坐標(biāo)等于世界坐標(biāo)經(jīng)過(guò)平移和縮放變換之后的結(jié)果。兩者之間的換算公式簡(jiǎn)單直接// 世界坐標(biāo) → 屏幕坐標(biāo) screenX (worldX - viewportCenterX) * zoom canvasWidth / 2; screenY (worldY - viewportCenterY) * zoom canvasHeight / 2; // 屏幕坐標(biāo) → 世界坐標(biāo)用于鼠標(biāo)定位 worldX (screenX - canvasWidth / 2) / zoom viewportCenterX; worldY (screenY - canvasHeight / 2) / zoom viewportCenterY;看似簡(jiǎn)單但真做起來(lái)有兩個(gè)常見(jiàn)坑。第一個(gè)坑是縮放中心。用戶滾動(dòng)滾輪時(shí)通常希望鼠標(biāo)指向的位置在縮放后保持不動(dòng)也就是“以鼠標(biāo)為中心縮放”。實(shí)現(xiàn)方式是先記錄鼠標(biāo)在縮放前位于哪個(gè)世界坐標(biāo)縮放后把這個(gè)世界坐標(biāo)重新映射回鼠標(biāo)的屏幕位置從而反推出新的視口中心。公式如下const worldPointBeforeZoom screenToWorld(mouseScreenX, mouseScreenY); zoom clamp(zoom * factor, minZoom, maxZoom); // 將世界點(diǎn)重新投影到鼠標(biāo)處反推出視口中心 viewportCenterX worldPointBeforeZoom.x - (mouseScreenX - canvasWidth / 2) / zoom; viewportCenterY worldPointBeforeZoom.y - (mouseScreenY - canvasHeight / 2) / zoom;第二個(gè)坑是縮放邊界。無(wú)限縮放會(huì)讓畫(huà)布漂到看不見(jiàn)的地方或者把場(chǎng)景縮到比一像素還小。我設(shè)置了最小 10%、最大 500% 的縮放范圍同時(shí)在實(shí)現(xiàn)上用clamp函數(shù)把計(jì)算后的 zoom 鎖死。另一個(gè)隱藏問(wèn)題是縮放值如果是浮點(diǎn)數(shù)累計(jì)十幾次滾輪之后精度漂移會(huì)讓視圖抖動(dòng)。我干脆在縮放事件里只保留兩位小數(shù)實(shí)測(cè)后用戶基本感覺(jué)不到。3.3 命中檢測(cè)與框選Canvas 渲染模式下沒(méi)有 DOM 可以點(diǎn)擊所有交互都要自己判斷“鼠標(biāo)落在了哪個(gè)元素上”。最樸素的辦法是遍歷全部節(jié)點(diǎn)逐個(gè)判斷鼠標(biāo)點(diǎn)是否落在節(jié)點(diǎn)矩形內(nèi)。節(jié)點(diǎn)數(shù)量少時(shí)這么做可以接受但節(jié)點(diǎn)一多每次 mousemove 都全量遍歷性能就開(kāi)始露餡。我的優(yōu)化是建立一套空間索引按網(wǎng)格劃分畫(huà)布區(qū)域節(jié)點(diǎn)按所在網(wǎng)格注冊(cè)鼠標(biāo)移動(dòng)時(shí)只檢測(cè)鼠標(biāo)所在網(wǎng)格及相鄰網(wǎng)格內(nèi)的節(jié)點(diǎn)遍歷量直接下降一個(gè)數(shù)量級(jí)。const gridSize 64; const grid new Mapstring, DiagramNode[](); function getGridKey(x: number, y: number): string { return ${Math.floor(x / gridSize)},${Math.floor(y / gridSize)}; }網(wǎng)格的鍵是坐標(biāo)除以格子大小的取整結(jié)果節(jié)點(diǎn)插入時(shí)按其中心點(diǎn)所在的格子歸入對(duì)應(yīng) key。命中檢測(cè)時(shí)只查詢鼠標(biāo)點(diǎn)所在的三個(gè)格子當(dāng)前格、左側(cè)格、上側(cè)格里的節(jié)點(diǎn)做精確矩形判斷??蜻x的做法類(lèi)似拖拽出橡皮筋選框時(shí)遍歷受影響網(wǎng)格里的節(jié)點(diǎn)只要節(jié)點(diǎn)的矩形和選框矩形有交集就視為被選中。這里要注意的是框選時(shí)需要把屏幕坐標(biāo)的選框反算成世界坐標(biāo)再來(lái)判斷因?yàn)楣?jié)點(diǎn)存儲(chǔ)的是世界坐標(biāo)直接拿屏幕坐標(biāo)去比對(duì)會(huì)因?yàn)榭s放平移的干擾而錯(cuò)誤。我在實(shí)際實(shí)現(xiàn)中還發(fā)現(xiàn)一個(gè)細(xì)節(jié)節(jié)點(diǎn)選中狀態(tài)應(yīng)顯示在渲染層之外。選中節(jié)點(diǎn)的邊框、陰影屬于表達(dá)層狀態(tài)直接掛在臨時(shí)狀態(tài)對(duì)象里不會(huì)污染數(shù)據(jù)模型。這樣圖數(shù)據(jù)導(dǎo)出時(shí)不會(huì)包含多余信息重開(kāi)畫(huà)布時(shí)也不需要重建選中狀態(tài)。4. 實(shí)操過(guò)程從空白畫(huà)布到可交互編輯器4.1 環(huán)境準(zhǔn)備與基礎(chǔ)依賴(lài)開(kāi)始編碼之前我先明確依賴(lài)清單。這里列的是我實(shí)際用到的最小集合能少裝的絕不多裝TypeScript類(lèi)型系統(tǒng)核心依賴(lài)Vite開(kāi)發(fā)服務(wù)器和構(gòu)建工具主打一個(gè)快無(wú) UI 框架純 Canvas 場(chǎng)景框架在這場(chǎng)合幫不上什么忙初始化方式非常標(biāo)準(zhǔn)npm create vitelatest diagram-design -- --template vanilla-ts cd diagram-design npm install為什么不用 Vue 或 React因?yàn)?Canvas 渲染的主場(chǎng)景不依賴(lài)框架的虛擬 DOM節(jié)點(diǎn)的增刪改全由數(shù)據(jù)層觸發(fā)重繪框架反而會(huì)引入不必要的橋接邏輯。UI 面板、屬性編輯器這些外圍組件我是寫(xiě)在一個(gè)簡(jiǎn)單的 div 層上的直接用原生事件綁定即可。4.2 核心實(shí)現(xiàn)的逐步推進(jìn)我按下面這張順序表逐步推進(jìn)開(kāi)發(fā)每完成一步都會(huì)做一次手動(dòng)測(cè)試確保沒(méi)有崩壞第一步數(shù)據(jù)模型層先寫(xiě)出DiagramNode、DiagramEdge、Diagram的類(lèi)型定義和增刪改查方法。這個(gè)階段不碰渲染所有操作都只是純數(shù)據(jù)變化。第二步基礎(chǔ)畫(huà)布渲染創(chuàng)建 Canvas 并畫(huà)出一批靜態(tài)節(jié)點(diǎn)和邊驗(yàn)證整個(gè)數(shù)據(jù)到圖形的映射關(guān)系。程序跑通后我加了一個(gè)背景網(wǎng)格線方便觀察坐標(biāo)位置是否正確。第三步鼠標(biāo)事件接入實(shí)現(xiàn)鼠標(biāo)按下、移動(dòng)、釋放的事件監(jiān)聽(tīng)區(qū)分左鍵和右鍵行為。切換畫(huà)布模式為拖拽模式實(shí)現(xiàn)節(jié)點(diǎn)跟隨鼠標(biāo)移動(dòng)。第四步滾輪縮放與拖拽平移把視口變換邏輯接入實(shí)現(xiàn)以鼠標(biāo)為中心的縮放和畫(huà)布拖拽平移。第五步連線創(chuàng)建在兩個(gè)端口之間創(chuàng)建貝塞爾曲線。按下鼠標(biāo)出線移動(dòng)鼠標(biāo)彎曲釋放鼠標(biāo)完成連接。第六步撤銷(xiāo)重做接入快照機(jī)制。每次操作完成后壓棧撤銷(xiāo)時(shí)恢復(fù)上一狀態(tài)。第七步性能優(yōu)化加入網(wǎng)格索引、視口剔除、渲染節(jié)流。每一步之間我都跑了固定測(cè)試用例建 20 個(gè)節(jié)點(diǎn)連 30 條邊然后移動(dòng)、縮放、撤銷(xiāo)、重做確保沒(méi)有回歸。整個(gè)開(kāi)發(fā)周期大概用了三周半每天投入兩到三小時(shí)。時(shí)間的大頭花在連線的交互適配和框選的邊界問(wèn)題上這兩個(gè)點(diǎn)最容易出細(xì)節(jié) bug。4.3 連線交互的完整實(shí)現(xiàn)連線是整個(gè)編輯器中交互邏輯最復(fù)雜的一塊我來(lái)重點(diǎn)展示它的實(shí)現(xiàn)路徑。連線創(chuàng)建屬于一種特殊模式用戶從源端口點(diǎn)擊按下此時(shí)畫(huà)布進(jìn)入connecting模式鼠標(biāo)移動(dòng)時(shí)需要實(shí)時(shí)繪制一條從源端口到鼠標(biāo)位置的“預(yù)覽連線”釋放鼠標(biāo)時(shí)程序判斷目標(biāo)位置是否為合法的目標(biāo)端口合法則正式創(chuàng)建邊不合法則丟棄。預(yù)覽連線的形狀我用的是三階貝塞爾曲線。為什么用貝塞爾而不是直線或者折線因?yàn)樨惾麪柷€能以一條平滑曲線優(yōu)雅地連接兩個(gè)端口且可以根據(jù)兩個(gè)端口的朝向自動(dòng)調(diào)整控制點(diǎn)不需要我在數(shù)據(jù)模型里存儲(chǔ)路徑信息。曲線的控制點(diǎn)確定規(guī)則是源端口朝向是左側(cè)控制點(diǎn)就向左邊延伸目標(biāo)端口朝向是右側(cè)控制點(diǎn)就向右邊延伸。延伸距離取兩端口之間距離的一半這樣曲線彎曲幅度適中不會(huì)過(guò)彎也不會(huì)太直。function buildEdgePath(source: Point, target: Point, sourceDirection: Direction, targetDirection: Direction): Path2D { const dist Math.hypot(target.x - source.x, target.y - source.y); const offset dist * 0.5; const c1 offsetByDirection(source, sourceDirection, offset); const c2 offsetByDirection(target, targetDirection, offset); const path new Path2D(); path.moveTo(source.x, source.y); path.bezierCurveTo(c1.x, c1.y, c2.x, c2.y, target.x, target.y); return path; }目標(biāo)端口的檢測(cè)也需要注意釋放鼠標(biāo)時(shí)取鼠標(biāo)所在坐標(biāo)調(diào)用空間索引找到覆蓋該坐標(biāo)的節(jié)點(diǎn)再判斷該節(jié)點(diǎn)上是否有端口在這個(gè)鼠標(biāo)位置附近。如果命中則創(chuàng)建正式邊如果沒(méi)有命中直接把預(yù)覽連線清掉。創(chuàng)建邊之前還多一步合法性校驗(yàn)一條邊不能連接同一個(gè)端口兩次也不允許從輸出端口連接到另一輸出端口具體取決于端口類(lèi)型定義。這些規(guī)則放在數(shù)據(jù)層渲染層不做判斷這樣可以保證無(wú)論如何操作底層數(shù)據(jù)不會(huì)進(jìn)入非法狀態(tài)。4.4 自動(dòng)布局與對(duì)齊輔助用戶手動(dòng)拖完一堆節(jié)點(diǎn)后圖必然歪歪扭扭。所以我在工具欄里加了兩個(gè)自動(dòng)整理功能層級(jí)布局和垂直/水平對(duì)齊。層級(jí)布局適用于流程類(lèi)圖把節(jié)點(diǎn)按照邊的方向分層同一層的節(jié)點(diǎn)排列成一列或一行層與層之間的距離相等。實(shí)現(xiàn)分三步拓?fù)渑判虼_定每個(gè)節(jié)點(diǎn)的層級(jí)。同一層的節(jié)點(diǎn)按原始橫向順序排列間距均勻。邊的交叉最小化我用的是一個(gè)簡(jiǎn)化的貪心算法逐層調(diào)整節(jié)點(diǎn)的順序讓相鄰層的邊交叉數(shù)量盡量小。垂直對(duì)齊和水平對(duì)齊的邏輯更簡(jiǎn)單選中多個(gè)節(jié)點(diǎn)取一個(gè)基準(zhǔn)坐標(biāo)比如最左側(cè)節(jié)點(diǎn)的 x 或所有選中節(jié)點(diǎn)的均值把其他節(jié)點(diǎn)的對(duì)應(yīng)坐標(biāo)改為基準(zhǔn)值即可。對(duì)齊操作不是一個(gè)“永久狀態(tài)”用戶隨時(shí)可以再做手動(dòng)調(diào)整所以它仍然落成數(shù)據(jù)層的坐標(biāo)修改配合撤銷(xiāo)重做棧一起使用沒(méi)有引入額外復(fù)雜度。4.5 性能優(yōu)化實(shí)戰(zhàn)性能問(wèn)題在節(jié)點(diǎn)數(shù)超過(guò) 300 之后開(kāi)始顯現(xiàn)。第一件導(dǎo)致卡頓的事是每次重繪都清空整個(gè)畫(huà)布并重畫(huà)全部節(jié)點(diǎn)和邊。優(yōu)化手段是視口剔除只繪制當(dāng)前視口可見(jiàn)范圍內(nèi)的元素function isInViewport(node: DiagramNode, viewport: Viewport): boolean { return ( node.x node.width viewport.left node.x viewport.right node.y node.height viewport.top node.y viewport.bottom ); }視口的邊界值直接由視口中心坐標(biāo)和縮放后的窗口尺寸計(jì)算得出。由于世界坐標(biāo)和視口邊界都經(jīng)過(guò)縮放換算這個(gè)判斷非常便宜。第二件導(dǎo)致卡頓的事是鼠標(biāo)移動(dòng)過(guò)程中觸發(fā)密集重繪。我引入了requestAnimationFrame節(jié)流移動(dòng)事件只更新?tīng)顟B(tài)標(biāo)記不直接重繪動(dòng)畫(huà)幀到來(lái)時(shí)檢查標(biāo)記若有變化才重繪。這樣把重繪頻率從 mousemove 的每次觸發(fā)可以高達(dá)幾百次/秒降到了屏幕刷新率60次/秒體驗(yàn)反而更平滑。第三件是字體渲染消耗。Canvas 每幀繪制大量文本字體解析很貴。優(yōu)化方案是把文本渲染單獨(dú)抽出來(lái)啟用ctx.font的緩存只有在字體字符串變化時(shí)才更新。實(shí)際測(cè)試中這個(gè)優(yōu)化帶來(lái)了 20% 左右的渲染耗時(shí)下降。5. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄5.1 高頻問(wèn)題速查表我把開(kāi)發(fā)過(guò)程中遇到的高頻問(wèn)題整理成一張速查表每一個(gè)都附了對(duì)應(yīng)的排查思路?,F(xiàn)象可能原因排查/解決方式拖拽節(jié)點(diǎn)時(shí)圖形跳動(dòng)事件綁定時(shí)誤用了局部坐標(biāo)而非全局坐標(biāo)確認(rèn)鼠標(biāo)事件計(jì)算的坐標(biāo)已通過(guò)getBoundingClientRect去除畫(huà)布偏移滾輪縮放后視圖抖動(dòng)縮放倍數(shù)是浮點(diǎn)累積導(dǎo)致精度漂移縮放后對(duì) zoom 值做四舍五入保留兩位小數(shù)連線總是對(duì)不準(zhǔn)目標(biāo)端口端口坐標(biāo)計(jì)算沒(méi)有考慮節(jié)點(diǎn)拖拽后的位移確保端口坐標(biāo)在每次渲染前按節(jié)點(diǎn)當(dāng)前世界坐標(biāo)重新計(jì)算撤銷(xiāo)一步后整個(gè)圖回到幾秒前快照覆蓋了整個(gè)畫(huà)布拖拽中間態(tài)也被記錄連續(xù)操作只記錄起始和結(jié)束快照中間態(tài)丟棄框選時(shí)選中了看不見(jiàn)的節(jié)點(diǎn)屏幕坐標(biāo)和世界坐標(biāo)混用框選矩形按視口變換反算為世界坐標(biāo)再進(jìn)行相交判斷Canvas 文字發(fā)虛或模糊高 DPI 屏幕下沒(méi)有做像素比適配畫(huà)布實(shí)際尺寸乘以window.devicePixelRatio再設(shè)置 Canvas 物理像素5.2 一個(gè)典型的數(shù)據(jù)恢復(fù) bug 復(fù)盤(pán)我想單獨(dú)講講之前遇到的一個(gè) bug它花了最長(zhǎng)的時(shí)間?,F(xiàn)象是用戶打開(kāi)一個(gè)保存過(guò)的圖形文件所有節(jié)點(diǎn)都在但節(jié)點(diǎn)之間原本存在的邊全部消失了或者邊的兩端都連到了奇怪的端口位置。初步排查時(shí)我懷疑是渲染層沒(méi)畫(huà)出來(lái)但控制臺(tái)輸出邊的數(shù)據(jù)是存在的。繼續(xù)追查發(fā)現(xiàn)邊的數(shù)據(jù)里記錄的sourcePortId和節(jié)點(diǎn)上的ports數(shù)組對(duì)不上——原來(lái)的端口 ID 在節(jié)點(diǎn)移動(dòng)時(shí)被重置了但邊的數(shù)據(jù)沒(méi)有同步更新。根因是節(jié)點(diǎn)有一個(gè)“重置端口”的操作邏輯會(huì)為端口生成新的 UUID但此時(shí)關(guān)聯(lián)邊仍然握著舊端口的 ID。因?yàn)闆](méi)有做級(jí)聯(lián)更新舊端口 ID 查無(wú)此人渲染層自然畫(huà)不出連線。修復(fù)辦法是在重置端口的邏輯里增加一步先遍歷所有關(guān)聯(lián)該節(jié)點(diǎn)的邊把邊的端口 ID 更新為新端口 ID然后再執(zhí)行重置操作。這個(gè)案例給到一個(gè)通用教訓(xùn)圖數(shù)據(jù)模型里凡是要改節(jié)點(diǎn)內(nèi)部標(biāo)識(shí)符ID的操作必須先檢查關(guān)聯(lián)邊的引用關(guān)系。這類(lèi)引用一致性問(wèn)題是圖編輯器的地雷往往不會(huì)當(dāng)場(chǎng)爆而是在用戶重新加載時(shí)爆。5.3 排除性能瓶頸的順序策略當(dāng)畫(huà)布開(kāi)始卡頓我的排查順序是先看是否在拖拽/縮放的動(dòng)畫(huà)幀里做了全量重繪。這是最常見(jiàn)的性能殺手占比超過(guò)一半。再看命中和渲染是否做了無(wú)謂的遍歷。例如每次鼠標(biāo)移動(dòng)觸發(fā)全量遍歷而不是網(wǎng)格索引。然后檢查是否有內(nèi)存泄漏導(dǎo)致的 GC 壓力典型情況是事件監(jiān)聽(tīng)器沒(méi)有解除。最后才看圖形本身的繪制開(kāi)銷(xiāo)比如陰影、漸變這類(lèi)視覺(jué)效果本身會(huì)拖慢渲染。前三個(gè)問(wèn)題都是代碼結(jié)構(gòu)問(wèn)題修復(fù)成本低效果立竿見(jiàn)影。第四個(gè)通常是為了視覺(jué)效果做出的主動(dòng)取舍需要靠視覺(jué)降級(jí)方案比如只在選中的節(jié)點(diǎn)上繪制陰影而不是所有節(jié)點(diǎn)來(lái)平衡。6. 關(guān)于擴(kuò)展方向的一點(diǎn)體會(huì)圖表設(shè)計(jì)做到這里基礎(chǔ)能力已經(jīng)完整了但我梳理下來(lái)發(fā)現(xiàn)還可以往幾個(gè)方向走。一個(gè)是把導(dǎo)出能力增強(qiáng)。目前只能導(dǎo)出 JSON 和圖片快照后續(xù)可以支持導(dǎo)出為 SVG 源文件、Markdown 內(nèi)嵌圖或者直接生成 PDF。導(dǎo)出 SVG 的時(shí)候需要注意Canvas 里的貝塞爾曲線要原樣轉(zhuǎn)成 SVG 的 path 元素文本節(jié)點(diǎn)需要轉(zhuǎn)成 text 元素并且保留字體的 family 和 size 屬性。另一個(gè)方向是多頁(yè)面支持。當(dāng)前的圖是一張單畫(huà)布如果業(yè)務(wù)上需要同時(shí)管理多張圖需要加一層項(xiàng)目對(duì)象把圖組織成樹(shù)形結(jié)構(gòu)支持跨圖復(fù)制節(jié)點(diǎn)和引用關(guān)系。再一個(gè)是協(xié)作能力。多人同時(shí)編輯同一張圖是個(gè)很誘人的擴(kuò)展。但要注意實(shí)時(shí)協(xié)作對(duì)數(shù)據(jù)模型的耦合度要求非常高節(jié)點(diǎn) ID 必須全局唯一、邊引用必須跨端保持一致、操作記錄需要支持 CRDT 或版本向量。我的體會(huì)是如果業(yè)務(wù)沒(méi)有剛需盡量晚點(diǎn)碰這一塊先保證單機(jī)體驗(yàn)足夠扎實(shí)。從實(shí)際交付的角度講單機(jī)圖表編輯器做好已經(jīng)能覆蓋大多數(shù)真實(shí)場(chǎng)景。做復(fù)雜系統(tǒng)的圖設(shè)計(jì)工具第一步永遠(yuǎn)是數(shù)據(jù)模型穩(wěn)不穩(wěn)而不是渲染漂不漂亮。我寫(xiě)的所有代碼里給之后項(xiàng)目復(fù)用最多的也是那套數(shù)據(jù)模型和狀態(tài)管理思路而不是任何一處渲染效果。最后分享一個(gè)實(shí)際調(diào)試中的小技巧節(jié)點(diǎn)多的時(shí)候不要靠人眼看圖找 bug我會(huì)在畫(huà)布里臨時(shí)按 F2 提前一層紋理把節(jié)點(diǎn) ID 直接打印在每個(gè)節(jié)點(diǎn)的左下角。雖然它不會(huì)出現(xiàn)在正式交付版本里但排查邊連錯(cuò)、端口錯(cuò)位、序列化還原不一致這類(lèi)問(wèn)題效率能提升好幾倍。
返回列表
PREV
查看更多資訊
NEXT
返回資訊列表
人人妻人人澡人人爽人人精品浪潮| 天天弄欧美| 综合色一区三区二区| 黄页大片在线观看| 亚欧高清v| 亚洲 暴爽 AV人人爽日日碰| 97就爱干| 美欧老女人97| 97国产天堂岛| 天堂日本亚洲欧美| 欧美亚洲日韩人妻在线观看| 综合久久婷婷| 99亚洲人人| 中文字幕诱惑制服人妻丝袜美丝袜美 | 六九九九| 日日嗷| 成人羞羞视频国产| 午夜成人福利影视| 可免费观看的av毛片中日美韩| 97人人模人人爽人人| 内射日韩大臀美女| 人妻熟女午夜精品在线| 日韩有码专区| 尻女朋友一夜| 欧美人人AAA| www.91逼逼.com| 日韩精品大香蕉伊人在线| 欧美激情性爱视频网站| 91另类| 91男女啊啊啊| 97精品熟女少妇一区| 狠狠操使劲操| 超碰 国产熟女精品一区| 欧美老妇曰批的视频| 美女啊啊啊啊啊啊| 最近2018中文字幕在线高清第一页| 999国产精品999| 91青青在线视频| 国产精品九九九| 天久久久噜噜噜久久国产精品爽爽 | 中文字幕三四五区| 中文字幕在线观看网页| 欧美超碰人妻97| 岛国爱情动作片在国产AV无码专区亚洲AV漫画| 五月天综合在线| 欧美激色| 九七毛片九九毛片| 久久宗合亚洲| 一区二区三区免费岛国片| 色色97爱| 久久草在线综合视频| 亚洲,欧美,春色,另类| 亚洲精品白浆高清久久久久久| 欧美性五月| 精品无码产区一区二| 亚洲AV小说| 九九九九精品精| 亚洲精品国产无码高清| 亚洲1区2区三区高清中文字幕| 国产婷婷一区| 青娱乐 成人娱乐在线| 亚洲中文字幕熟女少妇一区二区| www.狠狠| a男人的天堂| 91伊人大香蕉| 美女网站黄页| 人人操肉肉| 亚州中文字幕超碰97| 美女啪欧美一区| 超碰伊人在线| 波多野结衣AV无码一区| 无套内射性感少妇视频| 五月婷婷青青草娱乐伊人| 黄站在线免费观看| 国产不良强奸视频免费看| 热久久国产| 狠狠干狠狠色| 国产午夜福利合集| 成人乱人伦一区二区| 丁香六月啪| 夜夜国产一区| 性色A∨91| 韩国免费播放一级毛片| 婷婷操逼| 牛牛久久国产精品视频一二三| 综合亚洲欧美| 二男一女成人A片| 国模限制级电影| 亚洲男人天堂视频| 无码高清操逼网址| 91oumei| 亚州色图欧美| 欧美日韩大黄片| 欧美综合区| 色欲人妻一区二区在线| 尻女朋友一夜| 激情小说在线视频| 日本久久99| 999 久久久| 日产操逼| 91人妻视频在线| 9九九国产| 久久在线观看免费视频| 熟女丰满人妻一区| 懂色Av一区二区三区| 夜夜天天噜狠狠爱2021| 少妇天堂网络| 污色区网站| 亚洲aV性爱| 91AV天美在线视频| 日韩性爱电影一区| 欧美人妻制服| 爱我干综合| ai欧美亚洲小说| 人人模人人看| 97内射偷拍| 精品视频一二三中文| 丁香五月天堂网| 97久久国产精品| 三级激情网站| 亚欧操逼片在线观看| 中国乱伦一区二区| 视频二区熟女人妻| 日日日日做夜夜夜夜做无码97| 亚洲精品国产无码高清| 精品一二三区女同 | 亚洲国产欧美中文永久| 亚洲狠狠入| 五月丁香激情综合| 超碰色美女| 操逼操操操91| 精品国产乱码久久久| 五月丁香啪啪啪| 天天日天天干少妇日| 久久五月丁香| 五码视频在线观看| 色综合99999| 亚洲AV无码乱码在线观看性色| 美女91av| 天天干天天做| 玖玖爱在线视频免费观看| 国产精品久久9| PMv在线观看| av东京热男人的天堂| www.av在线视频| 久久精品日韩专区免费观看| 精品国产网站| 国产动漫操逼视频| 国产乱伦亚洲色图高清无码| 久草精品一区| 久操精品网| 午夜男人天堂| 另类小说综合网| 日韩久草| 日韩人妻精品中文字幕| 精品视频久久久久九九九九9999| 一起草三级AV电影在线观看| 东京热熟女亚洲视频网站| 日本欧美国内在线| 三级日本一区二区三区| 中文字幕第9页萱萱影音先锋| 久久香蕉国产线看观看猫咪av| 蜜乳AV.COM| 久久久久久久久久久久久女过产乱-少妇高潮一区二区三区喷水-成人AV | 精品久久久久黄少妇| 精品国产人成在线| 九九久久玖玖| 亚洲一区二区AV| 强奸熟女一区二区三区| 熟妇最新先锋一二三区| 伊人五月天激情| 天天综合官网| 啊啊啊久久久视频| 欧美日韩婷婷中文| 99色热| 午夜AV人气不卡| 在线观看色视频| 日韩欧美性吧婷婷乱伦大香蕉| 中日韩久久人妻一区二区| xxx0国产在线播放| 精品国产无码中文| 久草在线| 91久久婷婷| 精品久久大胆人体| 在线综合 亚洲 欧美中文字幕| 精品少妇人妻av久久免费| 99re黄| 亚熟hd视频在线| 久久性爱视频| 91丨熟女丨丰满熟女| 以及麻豆国产入口在线观看免费| 日本精品中文字幕视频| 久久riav中文精品| 视频在线观看一二三区| 五月天色图| 干干干天天| 午夜性生活av免费在线看| 天天92av| 国产综合网站在线播放| 欧美牲| 欧美情色贴图| 中国国国产一级特黄毛片| 99999久久精| 蜜臀久久99'精品久久久| 人妻天天爽夜夜爽爽| 天天躁日日躁XXXXYY| 大香焦A片| 五月丁香黄色网| 中文字幕成人乱码熟女精品国50 | 超碰 另类 欧美 | 国产精品一区二区三区,亚洲综合| 大色综合| 国产精品com| 九九九久久久| 无码操逼网| 日日日日做夜夜夜夜无码| 久久精品国产AV一区二区三区| 蜜臀久久99精品久久久久久-DVD原版全| 欧美人妖内射| 尤物网站91| 欧美人与动性人交a| 婷婷爽人人婷婷爽视频| 97超碰碰| 久久久国产av美女私房| 加勒比久久综合网高清| 六月婷婷综合| 97超碰色屌| 日韩精品操少妇| 亚洲一二三| 东京热大香焦| 色妇综合网| 亚洲婷婷丁香在线| 五月天黄色激情视频| 亚洲中文字母在线播放| 毛片视频白嫩| 偷拍综合亚洲| 夜夜高潮夜夜爽夜夜爱爱一区 | 99免费视频| 日本天堂网| 国产精品欧美激在线| 亚洲国产奇米影视久久| 久久婷婷色| 高清不卡国产| 欧美性少妇| 亚洲成人贴图| 国产人妖视频一区在线观看| 蜜臀少妇一区二区| 欧美精品欧美精品系列| 久操电影| 激情五月天社区| 天天日老熟妇| 中文字幕黄色一起草| 视频国产成人精品日本亚洲18| 亚洲国产成人精品999| 日本性爱不卡视频| 久久不卡一区二区| 久久久96精品| 亚洲乱码国产乱码精网站| 夜色五月天| 欧美999999| 亚洲se91| 六月激情婷婷| 国产精品久久久久久久久久久久久久久| 亚洲天堂少妇| 韩国三级理论在线| 中文字幕乱码人妻一区二区三区,99精品| 极品AV网站在线观看| 欧美97色| oumeisetu综合| 国产AV线| 天天视频黄网站| 手机午夜电影神马久久| 综合久久99| 天天操天天谢| 中文字幕在线观看永久| 白丝jkav| 日韩综合无码色欲vv| 红杏大香蕉| 综合网亚洲1| 韩日性爱av| 97人人中文网| 9色国产精品一区粉嫩| 精品超碰国产| 91四海无码日韩欧美| av一区二区三区四区| 日本熟妇熟色97一本在线观看| 国产精品高朝久久久久久久| 久久亚洲AV无码专区国产精品| 亚洲欧洲小说图片视频| 亚洲性天堂| 人妻久久久久久| 91在线丝袜| 日韩 欧美 视频 在线 一区| 蜜臀AV成人精品蜜臀AV久久| 久久这里只精品免费福利| 97超碰美国| 色淫网站优优视频| 大香蕉综合网| 男人女人18禁片免费看网站| 亚洲AO在线| 亚州熟女乱伦| 精品一区99999| 无码抄逼网| 亚洲天堂久久| 777超碰| 成人综合色网| 九九视频黄色片| 精品久久久久黄少妇| 欧美精品久久久久久久丰满| 日韩精品操少妇| 九九热久久99精品re| 国产人妻一区二区三区欧美毛片| 欧美另类精品xxxx| 啊啊啊啊操死我| 99热99re超碰精品| 国产乱色国产精品免费视| 大香蕉欧美国产日韩高潮| 国产无码高清操逼视频| 台湾佬中文娱乐网久久久久久久久久com | 欧美国产日韩清纯唯美| 久久久新亚洲AV| 九九热免费在线国产视频伊人五月| 亚洲国产丝袜熟女av| 人人做天天爱| 艹精品| 国产情色第一第二页在线观看| 亚洲?V无码专区在线电影| 亚洲老司机123专区| 狠狠色噜噜狠狠狠狠狠色综合久久| 综合一区二区影视| 懂色综合久久久| 大香蕉黄色一级片免费看| 智利AV在线网| 99热免费| 怡红院一区二区熟女人妻| 97视频免费播放| 天天爽夜夜欢视| 久久久久久AV无码免费网站| J?P?NESEHD熟女熟妇伦| 性感美女啊啊啊在线| 日韩欧美视频青青| 亚洲成人性| 综合五月天| 91爰爱欧美| 国产精品大香蕉| 国产精品午夜福利亚洲综合网| 熟女久久久| 青青草原综合久久大伊人精品| 中文一区在线视频| 久久黄片国产一区二区| 91综合天天| 精品一国2| 国产精品对白内射| 久热久| 蜜乳Av成人片网站| 婷婷色综合欧美日韩| av草草在线电影| 欧美狠狠操| 婬女免费一二三区A片| 成人小说视频在线精品欧美| 久草加勒比一区在线| 我想要啊 啊 啊| 怡春苑东京热| 国产超碰在线一区| 96爱综合| 欧美日韩人人精品| 97人肏| 99热日本| 九九激情网| 91天堂色男人的天堂| 亚洲日韩视频二区| 操操吧亚洲乱伦视频| 火箭成精品视频884必出精品| 在线免费试看60秒| 97在线青| 综合一区二区影视| 欧美日韩国产人人| 91 丝袜在线观看| 日日AAvv| 偷偷人人精品女女久久| 久久亚洲欧美一区二区三区-亚洲国产精品第一区二区 | 无码精品久久久天天影视| 人妻精品视频一区二区三区 | 九九热视频在线观看| 中文字幕狠狠玩| 久久av一级av少妇av高潮| 操啊国产| 福利社区午夜一区二区| 一线黄色免费性爱片| 精品无码不卡视频| 国产AV超爽| 日本色婷婷| 五月婷婷六月激情| 欧美日韩国第一区| 少妇与黑人高潮在线| 欧亚第一综合网| 日本999精品| 丰满丝袜少妇AV| 精品无码产区一区二| 五月天九九日国产精品一区二区三区| 91精品国产91久久久久久久久久久久| 99热婷婷一区二区三| 99re9| 另类图片欧美激情综合| 国产精品视频自拍在线| 国产一级操B视频| 九九黄色视频在线观看| 久久久精品国产亚洲伊人| 日韩亚洲美女一区久久| 97伪v| 99超碰网| 国产一区二区成人av在线播放| 2020中文在线一区二区三区| 高清无码国产亚洲| 精彩国产视频播放1区2区| 老司机免费视频在线91| 高潮9999外国| 久久草草亚洲蜜桃臀| 一级啊性爱在线视频| 激情综合亚洲| 校园春色中文字幕AV| 亚洲激情深爱文学小说网站| 国产强奸无码乱伦| 一区二区三区四区免费视频| 人妻天天操天天爽视频免费| 在线小视频| 夜夜爽夜夜操| 蜜乳AV一区二区三区四| 大香蕉欧美伊| 日本不卡一二区| 欧美日韩啪啪电影| 九九Av| 亚洲一本色码中文字幕| 久久国产乱子伦精品免费女,网站| 岛国黄片网站| 99色| 91麻豆天美国产欧美高潮| 九九九精品成人免费视频小说| 免费在线观看国内色片网站网址| 熟妇人妻一区二区三区| 久久久久久久久久久精| 欧美αv.com| 夜夜嗨一区二区| 日韩青久久| 人人干黄色| 粉嫩国产精品久久粉嫩| 久久久久久中文| 美女视频尤物网在线看| 亚洲欧洲美腿丝袜| 亚洲久久久久| 久久婷婷五月天| 一级黄碟| 欧美人与性动交a美精品| 97超碰无码网| 97欧美精品| 99热婷婷| 色吊丝 日日骚 清纯唯美| 成人乱人伦一区二区| www.四虎在线| 97超碰站| 18一区二区三区| www欧美91| 99精品成人免费看| 大香蕉2017| 免費黃色視頻觀看一| 99re在线视频国产| 日韩一性一交一A片俄罗斯| 99啪啪| 国产精品99精品视频网站| 96精品在线| 久久国产乱子伦精品免费女,网站| 国产成人无码久久精品| 97精品视频在线| 在线v中文字幕一区二区三区| 97av,com| 日韩无码专区| 东京热天堂网| 蜜桃精品一区二区三区ww| av黄图片在线观看| 久久久久久久久久久久黄色 | 亚洲欧美综合| 另类图片亚洲加勒比另类图片亚洲加勒比另类图片亚洲加勒比 | 久久久 国产精品| 9九九国产| 五月丁香在线| 色综合加勒比| 亚洲综合贴图91 | 蜜桃AV天堂| 婷色五月| 超碰日韩人妻| 97热视频在线观看| 久操婷婷| a'v在线资源| 99热在线不卡| 日本午夜精品理论片A级APP发布| 欧美亚洲首页| 禁十八久久| 欧美激情另类一区二区| 岛国A V在线免费看| 无卡一区=区| 就去色综合| 人妻熟女一区在| 欧美色院| 二级久久网| 亚洲砖码砖专无区2023| 男人的天堂网页| 东北老女人的激情视频| 在线观看日韩av不卡| 久久久日本电影| 97 国产一区| 欧美一品道| 国产超碰人人操| 超碰色综合| 亚洲色资源| 久久激情视频| 2017人人操,人人摸| 国产97/欧美| 九九热免费在线国产视频伊人五月| 亚洲天堂一区二区久久| 亚洲人妻日日日| 亚洲男人的天堂AV| 婷婷久久五月天| 操逼日批| 白丝一区| 男人的天堂三级| 超碰 另类 欧美 | 精品一区二区三区蜜桃臀赵总 | 97超碰天天| 日本韩欧美在线播放a| 毛片一区二区| 中文字幕丝袜| 一级黄碟| 国精综合一二三区影视| 97国产色综合| 九九Av| 欧亚性爱在线视频| 国产AAAAAABBBBB| 久久97精品久久久久久久不卡| 中文字幕AV片| 国产亚洲欧洲在线观看| www四虎| 丁香五月婷婷基地| 日本黄 R色 成 人网站| 亚洲国产一级精品毛一级精品看免费视频 | 亚洲人体视频在线观看| 精品一区二区三区麻豆| 97精品一区二区视频在线观看| 美女AV一区二区| 日本美女性生活久久久久久久| 激情综合婷婷| 五月综合婷婷久久网站| 无码丰满熟妇一区二区浪潮AV| 最新亚洲黄色免费电影 | 殴美牲| 天天爽天天操| 欧美熟女少妇| av中文字幕在线熟女| 久久99久久99久久99人受| 中日韩欧美精品无码AⅤ一区二区| 精品丝袜无码一区二区三APP| 中文字幕精品探花视频| 韩国三级色呦呦| 日日操夜夜操天天操免费观看麻豆| 欧美色91| 精品人妻av区天天看片| 麻豆天美一区二区| 九七超碰| 岛国福利在线精品播放| 婷婷三区| 偷拍超碰| 好爽免费视频,| ...日韩成人一区二区三区字幕| 亚洲人久久久网| 久久精品99| 97超碰影音| 日本在线999| 火箭成精品视频884必出精品| 青青久久艹| 亚洲无 码A片在线观看麻豆| 国产JDAV无码视频在线观看| 午夜福利av电影在线| 国产精品视频麻豆入口| 国产精品三级视频网站| 日韩精品一区二区三区四虎影视| 丰满翘臀美女影院视频| 超碰天天操你比| 超碰是碰在线观看| 日韩超碰97| 欧美日韩色综合网| 精品超碰国产| 无码人妻丰满熟妇奶水区毛片| 欧美熟女妇同| 欧美色图99| 日本成人在线不卡一区二区三区| 女同女同恋久久级三级| 久久婷婷综合国际产色怕| 又大又白奶子| 国产精品久久久久久久久久久久久久久久| 亚洲天堂少妇| 国产精品美女久久久久久网站| 岛国福利在线精品播放| 婷婷五月综合在线| 亚洲欧美日产国产91毛片| 亚洲不卡一| 欧洲在线性爱视频| 大香蕉丝袜一级片| 欧美天堂超碰97| 国产路线专区| 少妇无码av专区线| 五月综合久久| 2019午夜福利视频| 国产日韩精品suv| 欧美婷婷五月天| 少妇天堂网络| 久草在线| 精品性爱无码在线播放| 亚洲限制级| 搡老女人老91二区| 亚洲天堂 视频你懂的| 欧美性色综合网| 成人青青草原伊人| 国产高清MV操逼视频| 91原创在线观看| 超碰在线日韩一区| 强奸乱伦AV网站| 人妻密肉在线观看| 久久内射| 亚洲一区日韩| 91精品少妇搡搡搡| 国产精品视频麻豆入口| 亚洲91色在线| 欧美亚洲中文| 美女刺激久久国产欧美| 91日韩网站| 亚洲αv一区二区三区| 99天天超碰| 中文字幕丰满人妻日本| 国语人妻精彩刺激| 亚洲97在线| 六月婷婷综合| www.男人的天堂| 色香蕉影院| 97在线播放 | 女同亚洲欧美一二三区久久电影| 欧美色图另类图片| 强奸乱亚洲| 91九色丰满高潮| 婷婷丁香五月天亚洲天堂网| 日本熟妇色熟妇在线视频播放| 久久精品久久久久久久久| 天堂日本亚洲欧美| 天天夜躁日日躁狠狠2002| 有码免费观看| 久久伊人亚洲AV无码网站| 成人日韩中文字幕| www亚洲免费| 91精品微拍福利| 亚洲 se图 欧美电影| 家庭乱伦国产精品| 二对二中文字幕。| 蜜乳AV.COM| 91AV天美在线视频| 欧美一级A片在线看视频性色| 欧美黑人168页欧美黑人167| 婷婷丁香成人| 欧美高清第一页| 日本免费一区二| 亚洲天堂五月天国产| 秋霞一区二区三区四区五区六区七区| 日韩三A大片在线观看| 啊啊啊要高潮了| 久久久久国产精品片区无码直播| 丝袜美腿91| 97亚洲性爱| 天天日天天搞天天干| 中文字幕久久亚州无码| 欧美日综合| 色在线视频导航| 亚洲色91C| 欧美三级中文字幕hd| 超碰在线91| 第二页中文字幕| 操逼操网| 天天综合网网欲色| av久日| 99视频只有精品| 精品国产三级av韩国在线| 久久人爽| 国产九月婷婷| 超97在线精品视频| 97超碰欧美手机| AV丝袜少妇| 色五月首页| 免费a级毛片av无码久久精品中文字幕| 一区二区三区色综合| www.夜夜| 欧美一区二区三区蜜桃| 国产97亚洲| 综合一区二区影视| 久久啊哟| 香一区二区三区| 久久粉色| 日本五十路在线| 婷婷五月影院| 3D污黄视频在线观看| 嫩草 我啊~嗯~在线| 激情小说成人日本无码一| 色呦色呦色精品| 激情开心五月天| 欧美日韩天堂| 九九九九久久久| 超碰在线观看av不卡| 98久久超碰| 狠操91,com| 99999精品成人| 婷婷五月天小说| A 天堂在线观看视频| 被窝影院午夜看片无码| 国产精品一区二区三区免费视频| 亚洲超碰综合网| 久久久久亚洲一区女同性恋中文字幕| 久超碰在| 欧美日韩超碰在线| 色综合色| 久久久18| 婷婷天堂站| 男男H黄动漫啪啪无遮挡网站| 翔田千里无码中出中文字幕| 欧美日韩在线视频网站| 伊人操| 91亚洲欧美激情| 九九九不卡| 欧美第五页| 超碰一区二区| 97视频在线观看网站| 久久99草| 大屁股国产在线视频| 青草视频人妻在线观看| 97精品国产精品免费观看| 丰满少妇高潮无码| 日韩中文字幕2020| 福利一级版子| 97热视频在线观看| 亚洲天堂久| 日日爽熟女| 久久97视频| 久久啊啊啊| 国产精品熟女一区二区三区| 日韩一级二级三级免费看完整版| 成·人免费午夜在线观看| 老熟女91| 久久久久久久九九九九九九| 欧美黄色大片在线观看| 国产精品自产拍在线观看社区| WWW美腿丝袜香蕉中文| 色悠久久久av| 亚欧美色图| 97在线视频免费看| 国产成人久久精品蜜臀| 日本人妻伦在线中文字幕| 无码精品久久| 久久久免费的精品| 婷婷色色网| 国产丝袜美女诱惑| 96免费视频在线| 午夜a成v人电影| 久操视频资源站公开| 成人aⅴ一区二区三区| 日本国产欧美高清在线| 免费强奸av| 久久久久成人蜜桃精品| 天天爽爽爽爽| 久久大线蕉一区| 是还免费视频1727我| 综合亚州欧美| 欧美在线啊啊| 亚洲 自拍偷拍 欧美| 久久直播国产| 日韩pv中文| 亚洲情色在线| 精品日日人妻| 日本十八禁免费看污网站| 久草在线| 九九五月天| 日韩欧视频| 综合伊人网12色| 色天使亚洲综合在线观看| 5月婷婷6月六月丁香| 五月天激情网图片| 粉嫩AV输入| 深爱伊人影院| 久久中文字幕一区不卡| 日韩性爱长视频免费| 人人看人人插| 无码精品久久久天天影视| 操婷婷逼| 白 大 人妻 区 在线| 草B在线| 三级精品三级在线观看| 亚洲九月丁香| 亚洲男人的天堂AV| 性高潮久久久| 亚洲精品国产专区在线观看| 91精品久久久久久77777| 日本精品性生活久久久| 超碰97人妻在线| 福利视频一区二区微拍| 国产精品熟女AV中文字幕在线播放| 91天美传媒在线观看| 97综合| 欧美特大黄一级片片免费| 电影69乱码96| 校园春色亚洲色图| 蜜乳AV一区| 狠综合网| 美女天天干| 国产无码精品成人| 国产av美女被艹的乱叫| 国产又大又粗又长视频在线| 久久久久久久久国产| 中文字幕乱码人妻二区三区| 天天爽天天爽| 四虎av在线| 色综合久| 国产一区二区三区,在线观看观看| 久久夜夜| 99亚洲精品| 78m成人视线| 秋霞影音一区二区三区| 日本中文字幕熟妇| 天堂69亚洲精品中文字| 五月开心网| 久久爱97| 91干熟女| 蜜桃网熟妇| 永久免费发布性爱网| 美女黑人91神马| 久久国产在线一区二区| 校园春色综合色| av在线播放国产一区| 人妻在线臀日韩| 岛国黄片网站| 久久久久久久九九九九九九| 欧美国产精品久久九九| 啊啊啊操一区| 超碰九7| av在线一区二区三区| 少妇色综合| 免费人成?大片在线播放| 骚逼自拍99| 成人在线视频网| 熟妇一区,二区,三区。| 91碰碰| 69精品| 亚洲熟妇熟在线电影视频| 操婢日韩| 九区国产| 人妻人人操| 国产9熟妇视频网站| 性站 | 欧美18老人禁| 不卡码视频| 黄片免费看的| 3P丝袜熟女 色综合| 99热亚洲| 国产精品探花色| 亚洲黄色网址| www.夜夜| 极品白嫩美少妇在地板上位骑射淫水泛滥| 亚洲乱色视频一区、二区在线| 吖在线不卡一区二区国产剧情| 麻豆天美传媒在线视频天堂| 亚洲成人美女无吗| 这里都是精品| 国产精品久久久久久久久久久久久久久久久久 | 激情五月天插| 亚洲素人综合| 强上我不卡卡| 欧美性爱伊人| 超碰色老头| 男人的天堂在线| 婷婷久月| 国产精品原创巨作?v网站| 天天内射| 超碰人人在线| 国产在线强奸视频| 九九久精品| 日日夜夜青青草母狗| 日本www操操操| 精品人妻一二三| 91精品人妻啪啪间| 99热| 亚欧Av| 东京热大香蕉| 色香网| 超碰98综合网| 人妻久久久| 日韩激情中文字幕有码| 久久男人的天堂| 性欧美第一页| 欧美天堂亚洲电影院一区在线播放| 日本中文字幕在线视频| 精品毛片av一区二区| 激情婷婷丁香网| 夜夜爽33333| 欧美日本中字另类在线| 欧美第一页| 亚洲性综合11| 欧美亚州色的图| 久草精品国产蜜臀 | 中文字幕黑人大片| 一区二区三区 丝袜 高跟 美腿| 日本精品中文字幕视频| 日日干日日摸| 久久草视频污视频| 91国产大片| 日本 欧美 亚中文字幕| 国内毛片国产专区二| 天天草夜夜草高潮片| 综合色好色| 97色操| 97在线无精品| 人人超碰在线观看黄| 男人的天堂.com| 激情五月综合网| 亚洲色欲天天人妻无码系列专区| 黑人精品XXX一区一二区| 免费操逼91| 久久区| 天天天干977| 蜜臀久久99精品久久久久久成人小说 | 大香蕉伊人网WWWn0n| 国产福利精品最新在线| 欧美综合区| 2020国产精品| 国产精品久久久久无码AV会牛| 国产无马视频| 国语对白在线播放视频| 一类无码操逼视频| 国产精品一级特黄aaa大片在线观看| 免费精品无码一级毛片牛牛影视 | 亚洲情色91| 天天爽天天操| 麻豆天美国美国产| 夂久色| 亚洲色堂免费视频| 国产精品久久久蜜臀| Av色五月| 夜夜国产一区| 91网18| AV中文字幕剧情1区2区3| 亚洲电影91| 941超碰| 九九热精品视频六| 久久一二三四| 国产精品精品系列在线观看| 黄色片A级一区二区三区| 国产无码久久高清| 久草国产在线视频| 秋霞视频一区二区| 91人妻PORNY九色大屁股| 裸体美女免费看网站青草| 97AV爱| 欧美九九99久久精品| 久久一本大香蕉| 蜜桃精品视频一区二区三区| 97中文字幕一区| 久久精品一区二区| www.av家庭乱伦| 欧美国产精品| 精品人妻美妇91job| 色色色色综合网| 97中文天堂| 日韩专区数据列表-第3230页-精品国产一区二区三区香蕉 久久99熟女人妻中文字 | 3PAV乱伦视频| 乱伦一二三区| 簧片免费看视频| 欧美操逼一二三区| 殴美日韩m| 搡老女人老91妇女熟女| 熟女网站最新| 久久久青草青青国产亚洲免观精品高清完整版_97久久综合区小说区图片区,国精品 | 日韩视频中文字幕| a天堂视频| 久草精品热视| 是还免费视频1727我| 伊人久久AV诱惑悠悠| 国产视频第二页| 岛国毛片在线观看免费| 五月天激情小说| 啊啊啊97视频| 伊人麻豆传媒| 日韩欧美中文| 大奶啊啊好爽| 狠狠干妹子| 婷婷三区| 人人操人人狠狠操| 玖玖婷婷五月天| 丰满少妇高潮无码| 日韩精品人妻中文字幕不卡乱码| av毛片aaaaa免费看| 97最新在线播放视频| 日韩人体偷拍| 日本ZZ高免费A级视频| 狠狠操官网| 亚洲综合色网| 蜜桃臀一区二区aV| 国产精品永久免费10000| 成人性爱电影一区二区| 九九夜精品九九在线| 干少妇视频| 亚洲精品啪视频| 精品一区二区三区四区女 | 超碰人妻久久| 欧美久久伊人| 99综合自拍| 美女裸体无遮挡永久免费观看网站| 久久婷综合| 极品后入免费视频| 国产精品99精品视频网站| 九九九九久久久| 国产精品久久久久久夜夜夜夜| 人人操人人干xxx| 又大又黄国产| 97精品全部| 亚洲一区中文字幕一区| 久久↗↗| 国产日韩在线播放av| 欧美一区二区三区大综合| 天天日天天干天天操| 91精品国产高清久久久久久,亚洲成人| 91欧美美女日韩国产婷婷| 精品少妇高潮久久| 无码自拍SM| 新91视频.cmp| 最新岛国大片| 91丝袜激情在线| 色综合天天| 久久97资源 网| AV一起草在线| 99www.bibizy香蕉资源国产一区二区三区高清| 78精品| 操操AV电影| 国产精品午夜AV完会免费| 色婷婷电影| 欧美AB在线| 夜夜狼人妻| 久久久女人| 日本三级久| av一区二区三区不卡| 九九热免费视频| 精…码一二三区| 熟妇人妻一区二区三区| 黑人精品XXX一区一二区| 精彩国产视频播放1区2区| 无码外流操逼视频| 亚洲欧美日韩中文久久自慰| 激情国产乱伦Av| 精品日韩产品在线,日韩在线不卡视频,欧美日韩免费专区/久, | 情色大香蕉| 久久久18| 亚洲日韩国产精品| 久草婷婷| 国产高清26uuu| 成人青青草原伊人| 欧美激情亚洲色图| a级免费在线观看| 狠狠2050在线观看| 国产亚洲综合欧美一区| 中文区中文字幕免费看| 国产欧美一区二区| 岛国大片在线观看网站入口| 色色青青久久| 天美传媒av 在线| 欧美一区二区福利在线| 亚洲Av噜噜一区二区三区妖精| 亚洲精品日日夜夜52| 精品国产Av无码久久久伦古装| 五月激情影院| 成人五级久久| 日韩精品亚洲一二三| 夜夜操av亚洲一区二区| 91香蕉视频在线观看免费| 啊啊啊啊,啊啊好多水| 美女啊啊啊啊啊啊啊| 思思视频免费看网站| 人妻另类| 激情终合网| 在线精品福利免费播放| 国产毛片片精品天天看视频| 啊啊啊啊啊好舒服视频| 九九综合色| 亚洲色系另类精品国产| 欧美天天综合在线| 亚精品无码毛片一区二区三区| 欧美日韩国产色图在线| 另类亚洲一区二区三区| 久久久久久国产精品免费网站| 亭亭丁香激情| av网站免费看| 五十路成人在线视频二区三区| 暖暖精品二区三区观看| 欧美激情中文字幕另类小说| 人妻丝袜日本| 亚洲国产精品久久久久婷婷老年| 熟女日韩| 欧美强奸一区二区诱惑| 日韩欧美福利视频看看| 久九九九九九九九热| 久久e6只有精品| 成人看片网站| 成年人性爱日韩| 久久精品国产精品亚洲艾通辽熟妇 | 美女露胸露尿口| 日本欧美国内在线| 国产精品点击进入在线影院| 国产9l 大屁股| 伊人久久亚洲色欲综合网站 | 日本五十路在线| 六月婷婷五月丁香| 国产1769在线| 精品国产丝袜一区二区三区乱码| 丰满人妻一区二区三区四区| 精久久久| 精品成人女人久久| 欧美色偷拍| 国产毛片精品一区二区色欲黄A片| 日美免费黄片| 国产网红精品| 精品无码欧美三级| 久久久久久97| 啊啊啊久久| 久久久性少妇| 性欧美| 天天综合,91入口| 亚州精品人妻一二三区| 加勒比日本在线| 日韩激情视频| 超碰97玖玖爱| 四虎永久在线精品免费网址 | 欧美色图成人网一区二区| 裸体女人草逼视频播放一区,二区,三区,四区,五区 | 久久久久久99999国产精品| 啊啊啊啊好疼视频| 四虎视频在线观看| 老熟女熟妇| 欧美性爱一区| 亚洲熟女综合| 男人的天堂Va| 亚洲图片欧美另类综合免费视频大大香| 中文字幕综合人妻| 日韩欧美亚洲自拍偷拍| 色欧美综合| 日韩电影免费网站麻豆视频| 夜夜狠狠躁日日躁色视频| 国产五码丝袜屁眼| 青青草成人视频在线观看二区| 在线天堂999| 操死我了嗯嗯嗯| 日日干夜夜欢|