成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

前端圖形學實戰: 從零開發幾何畫板(vue3 + vite版)

開發 前端
我們繼續沿用上一篇文章幾何學在前端邊界計算中的應用和原理分析的工程, 由于幾何畫板相當于一個獨立的小應用, 具備一定的復雜度, 這里我們來對 vite 工程配置一下對 less 的支持。

前言

hello, 大家好, 我是徐小夕, 今天又到了我們的博學時間。

本文是 100+前端幾何學應用案例 專欄的第二篇文章, 在第一篇文章幾何學在前端邊界計算中的應用和原理分析 中我介紹了幾何學在前端領域里的應用, 同時用 vue3 帶大家一起實現了常見圖形的邊界計算算法, 并且分享了如何用幾何原理和Web Dom生成任意三角形的方式:

圖片

如果大家感興趣可以在 gitee 查看我的具體代碼實現: https://gitee.com/lowcode-china/euryd

接下來就繼續這個話題, 我們進一步擴展, 來從零實現一個幾何畫板。

你將收獲

  • vue3 + vite的實用技巧
  • 幾何畫板的基本開發思路

元素創建,

編輯,

拖拽,

圖層管理

撤銷和重做

導入導出

  • 利用幾何和代數學知識解決前端問題

demo演示

在分享方案之前, 我先給大家演示一下做好的demo, 這樣可以更好的理解我們接下來要做的事情:

圖片

技術實現

我們繼續沿用上一篇文章幾何學在前端邊界計算中的應用和原理分析的工程, 由于幾何畫板相當于一個獨立的小應用, 具備一定的復雜度, 這里我們來對 vite 工程配置一下對 less 的支持:

安裝 less 和less-loader (推薦yarn, pnpm)

在vite.config.ts里做如下配置:

export default defineConfig({
plugins: [vue()],
css: {
preprocessorOptions: {
less: {
modifyVars: {
hack: `true; @import (reference) "${path.resolve("src/base.less")}";`,
},
javascriptEnabled: true,
},
},
},
})

這樣配置完成之后我們就可以在 vite項目 里用 less 的方式寫樣式代碼了, modifyVars屬性里面的配置是為了指定 less 全局變量的地址, 這樣我們可以把主題, 通用樣式放在該目錄下, 以便直接在項目的任何頁面直接使用。

好了, 準備工作完成了, 我們開始接下來的實現部分。

1. 畫板搭建

畫板搭建主要是靜態和交互部分, 這里簡單和大家介紹一下基本構造:

圖片

上圖可知畫板主要分兩個部分:

  • 畫布區(包含記錄鼠標移動坐標的文本提示)
  • 側邊控件區

畫布的點陣背景我們用 css 的背景樣式實現, 這塊網上也有很多教程, 我就不一一和大家分析了,這里直接上實現的代碼, 大家可以拿來就用:

section .card {
position: relative;
height: 480px;
box-shadow: 0 0 4px rgba(0, 0, 0, 0.1);
background-image: radial-gradient(rgba(9, 89, 194, 0.3) 6%, transparent 0),
radial-gradient(#faf9f8 6%, transparent 0);
background-size: 10px 10px;
background-position: 0 0, 2px 2px;
}

整個畫板應用的基本結構如下:

圖片

如果大家對這一塊知識感興趣的可以參考我實現的代碼, 具體代碼地址: https://gitee.com/lowcode-china/euryd

接下來我們開始進行比較核心的方案設計。

2. 創建并繪制幾何圖形?

因為是畫版應用, 所以圖形的創建一定要簡單且靈活, 控制權交給用戶和鼠標, 所以這里實現的效果如下:

圖片

用戶只需要選擇對應的圖形, 用鼠標在畫布里拖動即可創建任意大小比例的圖形, 為了實現這一效果, 我們需要做如下準備:

  • 定義圖形的schema結構
  • 根據鼠標光標的位置計算圖形創建的元信息(圖形id, 頂點坐標, 寬高樣式等屬性)

(1)定義圖形的schema結構

像任何可視化低代碼產品一樣, 我們都需要一個統一且可擴展的組件schema結構, 目的是為了更好的識別組件和派發屬性。畫板應用里的圖形, 我們可以設計如下 schhema 結構:

type IShapeTypes = 'rect' | 'circle' | 'line';

interface IShapeProps {
id: string,
type: IShapeTypes,
style: {
width: number,
height: number,
left: number,
top: number,
...otherStyle
},
isEditable: boolean,
...otherSchema
}

具體配置只要具備通用性, 我們就可以結合自己的業務來配置。

定義好了 schema, 我們只需要實現對應的圖形即可, 這里以矩形為例和大家分享一下實現細節。

(2)根據鼠標光標的位置計算圖形創建的元信息

我們都知道, 要想通過鼠標拖動來創建任意一個矩形, 我們需要知道幾個條件:

  • 鼠標按下的初始點的坐標
  • 鼠標拖動過程中的實時位置

這兩個問題其實都可以在全局實現, 基于組件設計的原子化原則, 我們可以在畫布組件里捕獲并計算出鼠標的實時位置, 然后派發給其他組件消費, 這樣我們也可以是實現記錄鼠標移動坐標的文本提示 這一功能了。

在上一篇文章中已經介紹了如何用 vue3 的組合式函數來實現通用 hooks, 我們接下來要做的就是把 useMouse 獲取到的結果加工后讓其他組件能使用, 這里我用 vue3 的toRefs 來實現。先來看一下代碼:

// BaseBoard.tsx
<script setup lang="ts">
import { ref, onMounted, onUnmounted, toRefs } from "vue";
import { useMouse } from "../hooks/mouse";
import { getElPagePos } from "../utils/math";

const props = defineProps<{
msg: string;
onMouseChange: (x: number, y: number) => void;
}>();

const { onMouseChange } = toRefs(props);

const cardOffset = ref({ x: 0, y: 0 });
const boardDom = ref<any>(null);
const { x, y } = useMouse(window, (x, y) => {
onMouseChange && onMouseChange.value(x - cardOffset.value.x, y - cardOffset.value.y);
});

onMounted(() => {
const { x, y } = getElPagePos(boardDom.value);
cardOffset.value.x = x;
cardOffset.value.y = y;
});

onUnmounted(() => {});

defineExpose({ boardDom });
</script>

<template>
<section>
<h3>{{ msg }}</h3>
<div class="card" ref="boardDom">
<slot></slot>

<div style="user-select: none">
x: {{ x - cardOffset.x }} , y: {{ y - cardOffset.y }}
</div>
</div>
</section>
</template>

BaseBoard 就是我們的畫布組件, 我們使用這個組件可以在頁面上創建任意數量的畫布, 同時由于vue3 的組合函數支持使用defineProps 來定義組件的props, 所以我們可以通過它定義組件的屬性, 這里對外暴露了兩個屬性:

  • msg 用來在外部控制畫布的名稱
  • onMouseChange 用來將內部鼠標監聽的事件傳到外部, 讓外部可以拿到內部是事件運行時

我們使用 useMouse 的時候就可以實時拿到鼠標的x, y的絕對坐標, 再減去畫布在頁面的實際偏移cardOffset.x, cardOffset.y, 就可以得出鼠標在畫布中正確的坐標:

圖片

這樣我們就可以通過onMouseChange回調把鼠標相對畫布的坐標實時傳給父組件了:

const { x, y } = useMouse(window, (x, y) => { 
onMouseChange &&
onMouseChange.value(x - cardOffset.value.x, y - cardOffset.value.y);
});

同時我們在代碼中發現了 defineExpose, 這個 api 作用就是把需要暴露的數據導出,供父組件使用,相當于子傳父, 我們可以在父組件里拿到暴露的值, 在這里我們把畫布的 dom 暴露出來, 讓父組件可以拿到子組件的dom。

有了以上的前提, 我們就可以來創建矩形元素了, 為了更好的管理畫布中的元素, 我們定義一個元素集合canvasBox:

type shapeType = "rect" | "circle" | "line";

interface IBaseShapeProp {
type: shapeType;
key: string;
style: any;
}

const canvasBox = ref<{ [key in shapeType]: IBaseShapeProp[] }>({
rect: [],
circle: [],
line: [],
});

當用戶選擇一個圖形, 在畫布中按下鼠標的那一刻, 我們創建一個基本的元數據:

const handleMouseDown = () {
const { x, y } = mouseAbsPos.value;
if (curShape.value) {
templateDot = [x, y];
templateDot[2] = Date.now() + "";
canvasBox.value["rect"].push({
type: "rect",
key: templateDot[2],
style: {},
});
}
};

由上面的代碼可知, 我們會創建一個矩形的元數據, 包含了矩形的:

  • 元素類型
  • 矩形的唯一key(方便后續快速查找該圖形)
  • 矩形的初始化樣式

同時我們在 templateDot 變量中緩存了鼠標的初始位置, 方便后續生成矩形完整的元數據。

圖片

我們在圖中可以看出當拖動鼠標時矩形是實時跟隨鼠標創建的, 要想實現這個效果, 我們需要對鼠標的mousemove 進行監聽, 并動態更新矩形的元數據, 如下:

const handleMouseChange = (x: number, y: number) => {
mouseAbsPos.value = { x, y };
// 1.如果有選中的元素, 則判斷為移動當前選中元素
if (curSelect.value && templateDot.length) {
// something...
return;
}
// 2.否則則生成元素
const [a1, b1, key] = templateDot;
if (curShape.value && templateDot.length) {
let dx = x - a1;
let dy = y - b1;
let curIndex = canvasBox.value["rect"].findIndex((v) => v.key === key);
if (curIndex > -1) {
canvasBox.value["rect"][curIndex] = {
...canvasBox.value["rect"][curIndex],
style: {
left: (dx > 0 ? a1 : x) + "px",
top: (dy > 0 ? b1 : y) + "px",
width: Math.abs(dx) + "px",
height: Math.abs(dy) + "px",
},
};
}
}
};

由代碼可知我是通過實時改變矩形元素的 left 和 top 來實現矩形跟隨鼠標實時更新的, 我們使用 transform 也可以實現同樣的效果, 感興趣的朋友可以嘗試一下。

這里順便擴展一下, 我們平時看到的拖拽框架, 對組件進行多選操作時也用了同樣的方式, 通過鼠標拖拽滑動來產生多選區域:

圖片

感興趣的朋友可以把這個方案進行擴展, 實現更有意思的應用場景。

3. 移動, 編輯幾何圖形

有了上面創建元素的基礎, 我們繼續來實現移動和編輯元素的功能。

3.1 移動元素

首先我們需要找到當前要移動的元素, 然后動態改變它的位置, 因為每個元素我都設置唯一的key, 所以當元素被選中的時候我們就可以根據key找到此元素, 并只對該元素進行操作:

// 如果有選中的元素, 則判斷為移動當前選中元素
if (curSelect.value && templateDot.length) {
const [x0, y0] = templateDot;
canvasBox.value["rect"] = canvasBox.value["rect"].map((v) => {
if (v.key === curSelect.value) {
const { left, top } = v.style;
templateDot = [x, y];
return {
...v,
style: {
...v.style,
left: parseFloat(left) + (x - x0) + "px",
top: parseFloat(top) + (y - y0) + "px",
},
};
}
return v;
});
return;
}

以上代碼中主要是通過計算鼠標移動的位置差(通過緩存鼠標上一步的坐標)來改變元素的 left 和top 值, 在 mouseup 時重置緩存變量即可完成一次移動過程。

const handleMouseUp = () {
const { x, y } = mouseAbsPos.value;
if (curShape.value) {
// 1. 如果開始點和結束點一樣,則不創建
if (templateDot[0] === x && templateDot[1] === y) {
canvasBox.value["rect"] = canvasBox.value["rect"].filter(
(v) => v.key !== templateDot[2]
);
templateDot = [];
return;
}
}
// 重置
templateDot = [];
};

這里有一個細節需要注意, 就是如果在鼠標按下之后沒有拖動(也就是好點擊畫布的操作), 其實需要把mousedown創建的元素清空刪除, 所以才有了上述代碼的第一步判斷。

3.2 編輯元素

編輯元素其實和移動元素的模式差不多, 改變的是元素的靜態屬性, 比如我們可以編輯元素的背景顏色, 邊框樣式等, 這里我以刪除元素為例給大家介紹一下實現過程。

圖片

首先我們展示一下元素的 dom 結構:

<div
v-for="item in canvasBox.rect"
:key="item.key"
:class="['shape', 'rect', curSelect === item.key ? 'active' : '']"
:style="{
left: item.style.left,
top: item.style.top,
width: item.style.width,
height: item.style.height,
}"
:data-key="item.key"
@dblclick.stop="handleSelected(item.key)"
>
<span v-if="curSelect === item.key" @click="handleDel(item.key)">x</span>
</div>

當我們雙擊元素的時候, 我們通過key會給當前選中元素一個激活態, 此時v-if的刪除按鈕就會顯示, 我們綁定一個刪除方法 handleDel :

const handleDel = (key: string) => {
canvasBox.value["rect"] = canvasBox.value["rect"].filter((v) => v.key !== key);
curSelect.value = "";
templateDot = [];
};

刪除元素的方法是典型的單向操作, 比較簡單, 如果我們要改變元素的整體屬性, 我們需要設計一個屬性面板,并實現表單渲染器來動態的更新元素的屬性, 類似于 H5-Dooring 中的編輯面板:

圖片

在后面的文章中我會實現一個min版的屬性編輯器來完善我們的幾何畫板。

4. 圖層管理, 圖片導出等方案介紹

圖層管理也是編輯器常用的功能, 有了我們之前設計的 canvasBox, 我們就很容易實現一個圖層管理面板了, 我們只需要把存儲在canvasBox 元素數組遍歷到圖層面板, 并對其綁定操作方法即可實現涂圖層管理的常用功能, 比如:

  • 顯示隱藏
  • 快捷刪除
  • 批量刪除
  • 多選
  • 圖層移動
  • 切換元素

等等功能, 如 H5-Dooring 中的圖層管理面板:

圖片

責任編輯:武曉燕 來源: 趣談前端
相關推薦

2022-11-29 09:32:11

前端圖形學縮略圖

2016-01-22 11:09:40

計算機圖形學虛擬現實三維建模

2022-12-29 08:35:13

變換矩陣計算機圖形學

2022-01-13 08:13:14

Vue3 插件Vue應用

2024-12-30 14:40:20

2022-07-27 08:40:06

父子組件VUE3

2023-04-27 11:07:24

Setup語法糖Vue3

2022-08-09 10:00:57

ViteTypeScripVue3

2022-07-28 08:26:18

Vue3Uni-appVite

2021-11-26 05:59:31

Vue3 插件Vue應用

2024-10-18 10:49:03

Actions異步函數

2024-11-06 10:16:22

2021-12-01 08:11:44

Vue3 插件Vue應用

2024-09-05 08:50:11

2021-11-30 08:19:43

Vue3 插件Vue應用

2023-11-28 09:03:59

Vue.jsJavaScript

2021-04-20 10:28:49

計算機互聯網 技術

2022-08-15 07:34:36

vite項目Vue3

2022-09-06 12:20:30

Vue3CVCRUD

2023-03-13 07:52:13

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日韩在线视频观看 | a在线免费观看 | 一区二区三区回区在观看免费视频 | 日日夜夜精品视频 | 国产伦精品一区二区三区高清 | 一区二区在线视频 | 日韩在线观看一区二区三区 | www.99热这里只有精品 | 久久久成人网 | 一区在线播放 | 一区二区中文字幕 | 成人免费看片网 | 久久久精彩视频 | 精品国产31久久久久久 | 国产欧美精品一区二区色综合朱莉 | 日韩欧美一区二区三区四区 | 亚洲一区二区三区在线视频 | 亚洲三级在线观看 | 黄色毛片一级 | 久草热在线| 国产精品欧美精品 | 国产韩国精品一区二区三区 | 国产精品精品久久久 | 中文字幕在线网 | 国产一区二区精品在线观看 | 久草青青草 | 在线日韩欧美 | 日韩精品在线免费观看视频 | 国产精品久久久久久久久久久久午夜片 | 精品在线一区 | 精品一二 | 日本久久精品视频 | 中国毛片免费 | 一二三四在线视频观看社区 | 日韩视频国产 | 亚洲成a人片 | 91成人免费| 国产精久久久 | 国产精品一区久久久 | 日韩欧美国产一区二区三区 | 国产亚洲一区二区三区在线观看 |