自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

前端圖形學(xué)實(shí)戰(zhàn): 從零開(kāi)發(fā)幾何畫(huà)板(vue3 + vite版)

開(kāi)發(fā) 前端
我們繼續(xù)沿用上一篇文章幾何學(xué)在前端邊界計(jì)算中的應(yīng)用和原理分析的工程, 由于幾何畫(huà)板相當(dāng)于一個(gè)獨(dú)立的小應(yīng)用, 具備一定的復(fù)雜度, 這里我們來(lái)對(duì) vite 工程配置一下對(duì) less 的支持。

前言

hello, 大家好, 我是徐小夕, 今天又到了我們的博學(xué)時(shí)間。

本文是 100+前端幾何學(xué)應(yīng)用案例 專(zhuān)欄的第二篇文章, 在第一篇文章幾何學(xué)在前端邊界計(jì)算中的應(yīng)用和原理分析 中我介紹了幾何學(xué)在前端領(lǐng)域里的應(yīng)用, 同時(shí)用 vue3 帶大家一起實(shí)現(xiàn)了常見(jiàn)圖形的邊界計(jì)算算法, 并且分享了如何用幾何原理和Web Dom生成任意三角形的方式:

圖片

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

接下來(lái)就繼續(xù)這個(gè)話(huà)題, 我們進(jìn)一步擴(kuò)展, 來(lái)從零實(shí)現(xiàn)一個(gè)幾何畫(huà)板。

你將收獲

  • vue3 + vite的實(shí)用技巧
  • 幾何畫(huà)板的基本開(kāi)發(fā)思路

元素創(chuàng)建,

編輯,

拖拽,

圖層管理

撤銷(xiāo)和重做

導(dǎo)入導(dǎo)出

  • 利用幾何和代數(shù)學(xué)知識(shí)解決前端問(wèn)題

demo演示

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

圖片

技術(shù)實(shí)現(xiàn)

我們繼續(xù)沿用上一篇文章幾何學(xué)在前端邊界計(jì)算中的應(yīng)用和原理分析的工程, 由于幾何畫(huà)板相當(dāng)于一個(gè)獨(dú)立的小應(yīng)用, 具備一定的復(fù)雜度, 這里我們來(lái)對(duì) vite 工程配置一下對(duì) 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項(xiàng)目 里用 less 的方式寫(xiě)樣式代碼了, modifyVars屬性里面的配置是為了指定 less 全局變量的地址, 這樣我們可以把主題, 通用樣式放在該目錄下, 以便直接在項(xiàng)目的任何頁(yè)面直接使用。

好了, 準(zhǔn)備工作完成了, 我們開(kāi)始接下來(lái)的實(shí)現(xiàn)部分。

1. 畫(huà)板搭建

畫(huà)板搭建主要是靜態(tài)和交互部分, 這里簡(jiǎn)單和大家介紹一下基本構(gòu)造:

圖片

上圖可知畫(huà)板主要分兩個(gè)部分:

  • 畫(huà)布區(qū)(包含記錄鼠標(biāo)移動(dòng)坐標(biāo)的文本提示)
  • 側(cè)邊控件區(qū)

畫(huà)布的點(diǎn)陣背景我們用 css 的背景樣式實(shí)現(xiàn), 這塊網(wǎng)上也有很多教程, 我就不一一和大家分析了,這里直接上實(shí)現(xiàn)的代碼, 大家可以拿來(lái)就用:

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;
}

整個(gè)畫(huà)板應(yīng)用的基本結(jié)構(gòu)如下:

圖片

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

接下來(lái)我們開(kāi)始進(jìn)行比較核心的方案設(shè)計(jì)。

2. 創(chuàng)建并繪制幾何圖形?

因?yàn)槭钱?huà)版應(yīng)用, 所以圖形的創(chuàng)建一定要簡(jiǎn)單且靈活, 控制權(quán)交給用戶(hù)和鼠標(biāo), 所以這里實(shí)現(xiàn)的效果如下:

圖片

用戶(hù)只需要選擇對(duì)應(yīng)的圖形, 用鼠標(biāo)在畫(huà)布里拖動(dòng)即可創(chuàng)建任意大小比例的圖形, 為了實(shí)現(xiàn)這一效果, 我們需要做如下準(zhǔn)備:

  • 定義圖形的schema結(jié)構(gòu)
  • 根據(jù)鼠標(biāo)光標(biāo)的位置計(jì)算圖形創(chuàng)建的元信息(圖形id, 頂點(diǎn)坐標(biāo), 寬高樣式等屬性)

(1)定義圖形的schema結(jié)構(gòu)

像任何可視化低代碼產(chǎn)品一樣, 我們都需要一個(gè)統(tǒng)一且可擴(kuò)展的組件schema結(jié)構(gòu), 目的是為了更好的識(shí)別組件和派發(fā)屬性。畫(huà)板應(yīng)用里的圖形, 我們可以設(shè)計(jì)如下 schhema 結(jié)構(gòu):

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

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

具體配置只要具備通用性, 我們就可以結(jié)合自己的業(yè)務(wù)來(lái)配置。

定義好了 schema, 我們只需要實(shí)現(xiàn)對(duì)應(yīng)的圖形即可, 這里以矩形為例和大家分享一下實(shí)現(xiàn)細(xì)節(jié)。

(2)根據(jù)鼠標(biāo)光標(biāo)的位置計(jì)算圖形創(chuàng)建的元信息

我們都知道, 要想通過(guò)鼠標(biāo)拖動(dòng)來(lái)創(chuàng)建任意一個(gè)矩形, 我們需要知道幾個(gè)條件:

  • 鼠標(biāo)按下的初始點(diǎn)的坐標(biāo)
  • 鼠標(biāo)拖動(dòng)過(guò)程中的實(shí)時(shí)位置

這兩個(gè)問(wèn)題其實(shí)都可以在全局實(shí)現(xiàn), 基于組件設(shè)計(jì)的原子化原則, 我們可以在畫(huà)布組件里捕獲并計(jì)算出鼠標(biāo)的實(shí)時(shí)位置, 然后派發(fā)給其他組件消費(fèi), 這樣我們也可以是實(shí)現(xiàn)記錄鼠標(biāo)移動(dòng)坐標(biāo)的文本提示 這一功能了。

在上一篇文章中已經(jīng)介紹了如何用 vue3 的組合式函數(shù)來(lái)實(shí)現(xiàn)通用 hooks, 我們接下來(lái)要做的就是把 useMouse 獲取到的結(jié)果加工后讓其他組件能使用, 這里我用 vue3 的toRefs 來(lái)實(shí)現(xiàn)。先來(lái)看一下代碼:

// 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 就是我們的畫(huà)布組件, 我們使用這個(gè)組件可以在頁(yè)面上創(chuàng)建任意數(shù)量的畫(huà)布, 同時(shí)由于vue3 的組合函數(shù)支持使用defineProps 來(lái)定義組件的props, 所以我們可以通過(guò)它定義組件的屬性, 這里對(duì)外暴露了兩個(gè)屬性:

  • msg 用來(lái)在外部控制畫(huà)布的名稱(chēng)
  • onMouseChange 用來(lái)將內(nèi)部鼠標(biāo)監(jiān)聽(tīng)的事件傳到外部, 讓外部可以拿到內(nèi)部是事件運(yùn)行時(shí)

我們使用 useMouse 的時(shí)候就可以實(shí)時(shí)拿到鼠標(biāo)的x, y的絕對(duì)坐標(biāo), 再減去畫(huà)布在頁(yè)面的實(shí)際偏移cardOffset.x, cardOffset.y, 就可以得出鼠標(biāo)在畫(huà)布中正確的坐標(biāo):

圖片

這樣我們就可以通過(guò)onMouseChange回調(diào)把鼠標(biāo)相對(duì)畫(huà)布的坐標(biāo)實(shí)時(shí)傳給父組件了:

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

同時(shí)我們?cè)诖a中發(fā)現(xiàn)了 defineExpose, 這個(gè) api 作用就是把需要暴露的數(shù)據(jù)導(dǎo)出,供父組件使用,相當(dāng)于子傳父, 我們可以在父組件里拿到暴露的值, 在這里我們把畫(huà)布的 dom 暴露出來(lái), 讓父組件可以拿到子組件的dom。

有了以上的前提, 我們就可以來(lái)創(chuàng)建矩形元素了, 為了更好的管理畫(huà)布中的元素, 我們定義一個(gè)元素集合canvasBox:

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

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

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

當(dāng)用戶(hù)選擇一個(gè)圖形, 在畫(huà)布中按下鼠標(biāo)的那一刻, 我們創(chuàng)建一個(gè)基本的元數(shù)據(jù):

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: {},
});
}
};

由上面的代碼可知, 我們會(huì)創(chuàng)建一個(gè)矩形的元數(shù)據(jù), 包含了矩形的:

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

同時(shí)我們?cè)?nbsp;templateDot 變量中緩存了鼠標(biāo)的初始位置, 方便后續(xù)生成矩形完整的元數(shù)據(jù)。

圖片

我們?cè)趫D中可以看出當(dāng)拖動(dòng)鼠標(biāo)時(shí)矩形是實(shí)時(shí)跟隨鼠標(biāo)創(chuàng)建的, 要想實(shí)現(xiàn)這個(gè)效果, 我們需要對(duì)鼠標(biāo)的mousemove 進(jìn)行監(jiān)聽(tīng), 并動(dòng)態(tài)更新矩形的元數(shù)據(jù), 如下:

const handleMouseChange = (x: number, y: number) => {
mouseAbsPos.value = { x, y };
// 1.如果有選中的元素, 則判斷為移動(dòng)當(dāng)前選中元素
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",
},
};
}
}
};

由代碼可知我是通過(guò)實(shí)時(shí)改變矩形元素的 left 和 top 來(lái)實(shí)現(xiàn)矩形跟隨鼠標(biāo)實(shí)時(shí)更新的, 我們使用 transform 也可以實(shí)現(xiàn)同樣的效果, 感興趣的朋友可以嘗試一下。

這里順便擴(kuò)展一下, 我們平時(shí)看到的拖拽框架, 對(duì)組件進(jìn)行多選操作時(shí)也用了同樣的方式, 通過(guò)鼠標(biāo)拖拽滑動(dòng)來(lái)產(chǎn)生多選區(qū)域:

圖片

感興趣的朋友可以把這個(gè)方案進(jìn)行擴(kuò)展, 實(shí)現(xiàn)更有意思的應(yīng)用場(chǎng)景。

3. 移動(dòng), 編輯幾何圖形

有了上面創(chuàng)建元素的基礎(chǔ), 我們繼續(xù)來(lái)實(shí)現(xiàn)移動(dòng)和編輯元素的功能。

3.1 移動(dòng)元素

首先我們需要找到當(dāng)前要移動(dòng)的元素, 然后動(dòng)態(tài)改變它的位置, 因?yàn)槊總€(gè)元素我都設(shè)置唯一的key, 所以當(dāng)元素被選中的時(shí)候我們就可以根據(jù)key找到此元素, 并只對(duì)該元素進(jìn)行操作:

// 如果有選中的元素, 則判斷為移動(dòng)當(dāng)前選中元素
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;
}

以上代碼中主要是通過(guò)計(jì)算鼠標(biāo)移動(dòng)的位置差(通過(guò)緩存鼠標(biāo)上一步的坐標(biāo))來(lái)改變?cè)氐?nbsp;left 和top 值, 在 mouseup 時(shí)重置緩存變量即可完成一次移動(dòng)過(guò)程。

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

這里有一個(gè)細(xì)節(jié)需要注意, 就是如果在鼠標(biāo)按下之后沒(méi)有拖動(dòng)(也就是好點(diǎn)擊畫(huà)布的操作), 其實(shí)需要把mousedown創(chuàng)建的元素清空刪除, 所以才有了上述代碼的第一步判斷。

3.2 編輯元素

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

圖片

首先我們展示一下元素的 dom 結(jié)構(gòu):

<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>

當(dāng)我們雙擊元素的時(shí)候, 我們通過(guò)key會(huì)給當(dāng)前選中元素一個(gè)激活態(tài), 此時(shí)v-if的刪除按鈕就會(huì)顯示, 我們綁定一個(gè)刪除方法 handleDel :

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

刪除元素的方法是典型的單向操作, 比較簡(jiǎn)單, 如果我們要改變?cè)氐恼w屬性, 我們需要設(shè)計(jì)一個(gè)屬性面板,并實(shí)現(xiàn)表單渲染器來(lái)動(dòng)態(tài)的更新元素的屬性, 類(lèi)似于 H5-Dooring 中的編輯面板:

圖片

在后面的文章中我會(huì)實(shí)現(xiàn)一個(gè)min版的屬性編輯器來(lái)完善我們的幾何畫(huà)板。

4. 圖層管理, 圖片導(dǎo)出等方案介紹

圖層管理也是編輯器常用的功能, 有了我們之前設(shè)計(jì)的 canvasBox, 我們就很容易實(shí)現(xiàn)一個(gè)圖層管理面板了, 我們只需要把存儲(chǔ)在canvasBox 元素?cái)?shù)組遍歷到圖層面板, 并對(duì)其綁定操作方法即可實(shí)現(xiàn)涂圖層管理的常用功能, 比如:

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

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

圖片

責(zé)任編輯:武曉燕 來(lái)源: 趣談前端
相關(guān)推薦

2022-11-29 09:32:11

前端圖形學(xué)縮略圖

2022-12-29 08:35:13

變換矩陣計(jì)算機(jī)圖形學(xué)

2016-01-22 11:09:40

計(jì)算機(jī)圖形學(xué)虛擬現(xiàn)實(shí)三維建模

2022-01-13 08:13:14

Vue3 插件Vue應(yīng)用

2024-12-30 14:40:20

2022-07-27 08:40:06

父子組件VUE3

2023-04-27 11:07:24

Setup語(yǔ)法糖Vue3

2022-08-09 10:00:57

ViteTypeScripVue3

2022-07-28 08:26:18

Vue3Uni-appVite

2021-11-26 05:59:31

Vue3 插件Vue應(yīng)用

2024-10-18 10:49:03

Actions異步函數(shù)

2021-12-01 08:11:44

Vue3 插件Vue應(yīng)用

2024-11-06 10:16:22

2024-09-05 08:50:11

2021-04-20 10:28:49

計(jì)算機(jī)互聯(lián)網(wǎng) 技術(shù)

2021-11-30 08:19:43

Vue3 插件Vue應(yīng)用

2023-11-28 09:03:59

Vue.jsJavaScript

2022-08-15 07:34:36

vite項(xiàng)目Vue3

2022-09-06 12:20:30

Vue3CVCRUD

2023-03-13 07:52:13

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)