Vue實現(xiàn)多點涂鴉效果的示例代碼
效果展示
單點效果

多點效果

創(chuàng)建畫布
創(chuàng)建畫布并設(shè)置事件處理器
<canvas
ref="board"
style="background-color: #2b2d42"
width="1000"
height="1000"
@touchstart="handleStart"
@touchmove="handleMove"
@touchend="handleEnd"
/>獲取畫布并初始化畫筆信息
onMounted(() => {
initPointer();
});
const board = ref();
const cxt = ref();
const lineWidth = 4;
const initPointer = () => {
cxt.value = board.value.getContext('2d');
window.MeApi?.mainWindowLoaded();
cxt.value.strokeStyle = 'red';
cxt.value.fillStyle = 'red';
cxt.value.lineWidth = lineWidth;
};觸摸事件處理
基礎(chǔ)知識
唯一地識別和觸摸平面接觸的點的值。
這個值在這根手指(或觸摸筆等)所引發(fā)的所有事件中保持一致,直到它離開觸摸平面。
該屬性返回一個TouchList:
- 對于
touchstart事件,這個TouchList對象列出在此次事件中新增加的觸點。 - 對于
touchmove事件,列出和上一次事件相比較,發(fā)生了變化的觸點。 - 對于 touchend 事件,
changedTouches是已經(jīng)從觸摸面的離開的觸點的集合(也就是說,手指已經(jīng)離開了屏幕/觸摸面)。
拷貝觸摸點
瀏覽器會復(fù)用觸摸點,通過拷貝只記錄差異點和唯一標(biāo)識符替換引用整個對象的方式進(jìn)行優(yōu)化
type Point = {
identifier: number,//觸摸點什么標(biāo)識
//觸摸點坐標(biāo)
x: number,
y: number
};
const copyTouch = (touch: Touch) => {
return {
identifier: touch.identifier,
x: touch.pageX,
y: touch.pageY
};
};查找觸摸點
通過遍歷 activityTouches 數(shù)組來尋找與給定標(biāo)記相匹配的觸摸點,返回該觸摸點在數(shù)組中的下標(biāo)。
const activityTouchIndexById = (idToFind: number) => {
for (let i = 0; i < activityTouches.length; i++) {
const id = activityTouches[i].identifier;
if (id === idToFind) {
return i;
}
}
return -1;
};跟蹤所有觸摸點以實現(xiàn)多點觸控
//跟蹤當(dāng)前存在的所有觸摸點 const activityTouches: Point[] = [];
新增觸摸事件
當(dāng)屏幕上出現(xiàn)新的觸摸點touchstart事件被觸發(fā),handleStart 函數(shù)被觸發(fā)。此時,要收集記錄觸摸點并在觸摸點處畫圓:
const handleStart = (evt: TouchEvent) => {
//獲取所有新增的點
const touches = evt.changedTouches;
for (let i = 0; i < touches.length; i++) {
//收集觸摸點
activityTouches.push(copyTouch(touches[i]));
//畫圓
cxt.value.beginPath();
drawCircle(touches[i].clientX, touches[i].clientY)
}
};觸摸移動時
當(dāng) touchmove 事件被觸發(fā)時,從而將調(diào)用handleMove() 函數(shù),此時按照路徑繪制線:
const handleMove = (evt: TouchEvent) => {
evt.preventDefault();
const touches = evt.changedTouches;
for (let i = 0; i < touches.length; i++) {
const indexById = activityTouchIndexById(touches[i].identifier);
if (indexById >= 0) {
cxt.value.beginPath();
cxt.value.moveTo(activityTouches[indexById].x, activityTouches[indexById].y);
cxt.value.lineTo(touches[i].clientX, touches[i].pageY);
cxt.value.stroke();
//更新緩存信息
activityTouches.splice(indexById, 1, copyTouch(touches[i]));
}
}
};首先遍歷所有發(fā)生移動的觸摸點。通過讀取每個觸摸點的 Touch.identifier 屬性,從緩存中讀取每個觸摸點在變化前的起點。這樣取得每個觸摸點之前位置的坐標(biāo),進(jìn)而進(jìn)行繪制。
觸摸結(jié)束處理
通過調(diào)用 handleEnd() 函數(shù)來處理觸摸結(jié)束事件:
const handleEnd = (evt: TouchEvent) => {
evt.preventDefault();
const touches = evt.changedTouches;
for (let i = 0; i < touches.length; i++) {
const indexById = activityTouchIndexById(touches[i].identifier);
if (indexById >= 0) {
cxt.value.beginPath();
cxt.value.moveTo(activityTouches[indexById].x, activityTouches[indexById].y);
cxt.value.lineTo(touches[i].clientX, touches[i].clientY);
drawCircle(touches[i].clientX, touches[i].clientY)
//移除緩存
activityTouches.splice(indexById, 1);
}
}
};類似handleMove ,首先遍歷所有事件,并讀取緩存。在事件觸發(fā)點畫個圓,然后將對應(yīng)的觸摸對象從緩存中移除。
其他
移動端和PC端所對應(yīng)的時間不同。詳情可見鼠標(biāo)事件和觸摸事件文檔。
移動端的觸摸點信息被封裝在Touch中,通過Touch.clientX 和Touch.clientX 讀取當(dāng)前坐標(biāo)
移動端的觸摸點信息被封裝在MouseEvent中
完整代碼
<template>
<canvas
ref="board"
style="background-color: #2b2d42"
width="1000"
height="1000"
@touchstart="handleStart"
@touchmove="handleMove"
@touchend="handleEnd"
></canvas>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
const board = ref();
const cxt = ref();
const lineWidth = 4;
onMounted(() => {
initPointer();
});
const initPointer = () => {
cxt.value = board.value.getContext('2d');
window.MeApi?.mainWindowLoaded();
cxt.value.strokeStyle = 'red';
cxt.value.fillStyle = 'red';
cxt.value.lineWidth = lineWidth;
};
type Point = {
identifier: number,
x: number,
y: number
};
const activityTouches: Point[] = [];
const copyTouch = (touch: Touch) => {
return {
identifier: touch.identifier,
x: touch.pageX,
y: touch.pageY
};
};
const activityTouchIndexById = (idToFind: number) => {
for (let i = 0; i < activityTouches.length; i++) {
const id = activityTouches[i].identifier;
if (id === idToFind) {
return i;
}
}
return -1;
};
const handleStart = (evt: TouchEvent) => {
const touches = evt.changedTouches;
for (let i = 0; i < touches.length; i++) {
activityTouches.push(copyTouch(touches[i]));
cxt.value.beginPath();
drawCircle(touches[i].clientX, touches[i].clientY)
}
};
const handleMove = (evt: TouchEvent) => {
evt.preventDefault();
const touches = evt.changedTouches;
for (let i = 0; i < touches.length; i++) {
const indexById = activityTouchIndexById(touches[i].identifier);
if (indexById >= 0) {
cxt.value.beginPath();
cxt.value.moveTo(activityTouches[indexById].x, activityTouches[indexById].y);
cxt.value.lineTo(touches[i].clientX, touches[i].pageY);
cxt.value.stroke();
activityTouches.splice(indexById, 1, copyTouch(touches[i]));
}
}
};
const handleEnd = (evt: TouchEvent) => {
evt.preventDefault();
const touches = evt.changedTouches;
for (let i = 0; i < touches.length; i++) {
const indexById = activityTouchIndexById(touches[i].identifier);
if (indexById >= 0) {
cxt.value.beginPath();
cxt.value.moveTo(activityTouches[indexById].x, activityTouches[indexById].y);
cxt.value.lineTo(touches[i].clientX, touches[i].clientY);
drawCircle(touches[i].clientX, touches[i].clientY)
activityTouches.splice(indexById, 1);
}
}
};
const drawCircle = (x:number,y:number) => {
cxt.value.arc(x, y, lineWidth / 2, 0, 2 * Math.PI, false);
cxt.value.fill();
}
</script>到此這篇關(guān)于Vue實現(xiàn)多點涂鴉效果的示例代碼的文章就介紹到這了,更多相關(guān)Vue多點涂鴉內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue 實現(xiàn)列表動態(tài)添加和刪除的兩種方法小結(jié)
今天小編就為大家分享一篇Vue 實現(xiàn)列表動態(tài)添加和刪除的兩種方法小結(jié),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09
Vue3中的createGlobalState用法及示例詳解
createGlobalState 是 Vue 3 中一種管理全局狀態(tài)的簡便方式,通常用于管理多個組件間共享的狀態(tài),由 @vueuse/core 提供的,允許創(chuàng)建一個響應(yīng)式的全局狀態(tài),本文給大家介紹了Vue3中的createGlobalState用法及示例,需要的朋友可以參考下2024-10-10
vue如何實現(xiàn)Json格式數(shù)據(jù)展示
這篇文章主要介紹了vue如何實現(xiàn)Json格式數(shù)據(jù)展示,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-04-04
Vue模擬響應(yīng)式原理底層代碼實現(xiàn)的示例
最近去面試的人都會有這個體會,去年面試官只問我怎么用vue,今年開始問我vue響應(yīng)式原理,本文就詳細(xì)的介紹一下2021-08-08
Vue3警告:Failed to resolve component:XXX的詳細(xì)解決辦法
最近在一個vue3項目中遇到了報錯,整理了些解決辦法,這篇文章主要給大家介紹了關(guān)于Vue3警告:Failed to resolve component:XXX的詳細(xì)解決辦法,文中介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05
vxe-table中vxe-grid(高級表格)的使用方法舉例
vxe-table是一個基于vue的表格組件,下面這篇文章主要給大家介紹了關(guān)于vxe-table中vxe-grid(高級表格)的使用方法,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-05-05

