feat: 画笔工具支持触摸屏

This commit is contained in:
jiakunguo 2021-05-20 15:07:30 +08:00
parent 1cb955e287
commit d67862f282

View File

@ -1,256 +1,295 @@
<template> <template>
<div class="writing-board" ref="writingBoardRef"> <div class="writing-board" ref="writingBoardRef">
<canvas class="canvas" ref="canvasRef" <canvas class="canvas" ref="canvasRef"
@mousedown="$event => handleMousedown($event)" @mousedown="$event => handleMousedown($event)"
@mousemove="$event => handleMousemove($event)" @mousemove="$event => handleMousemove($event)"
@mouseup="handleMouseup()" @mouseup="handleMouseup()"
@mouseleave="handleMouseup(); mouseInCanvas = false" @touchstart.prevent="$event => { handleTouchdown($event);mouseInCanvas = true }"
@mouseenter="mouseInCanvas = true" @touchmove.prevent="$event => handleTouchmove($event)"
></canvas> @touchend="handleMouseup();mouseInCanvas = false"
@mouseleave="handleMouseup(); mouseInCanvas = false"
<div @mouseenter="mouseInCanvas = true"
class="pen" ></canvas>
:style="{
left: mouse.x - penSize / 2 + 'px', <div
top: mouse.y - 36 + penSize / 2 + 'px', class="pen"
color: color, :style="{
}" left: mouse.x - penSize / 2 + 'px',
v-if="mouseInCanvas && model === 'pen'" top: mouse.y - 36 + penSize / 2 + 'px',
><IconWrite class="icon" size="36" /></div> color: color,
}"
<div v-if="mouseInCanvas && model === 'pen'"
class="eraser" ><IconWrite class="icon" size="36" /></div>
:style="{
left: mouse.x - rubberSize / 2 + 'px', <div
top: mouse.y - rubberSize / 2 + 'px', class="eraser"
width: rubberSize + 'px', :style="{
height: rubberSize + 'px', left: mouse.x - rubberSize / 2 + 'px',
}" top: mouse.y - rubberSize / 2 + 'px',
v-if="mouseInCanvas && model === 'eraser'" width: rubberSize + 'px',
></div> height: rubberSize + 'px',
</div> }"
</template> v-if="mouseInCanvas && model === 'eraser'"
></div>
<script lang="ts"> </div>
import { defineComponent, onMounted, PropType, reactive, ref } from 'vue' </template>
const penSize = 6 <script lang="ts">
const rubberSize = 80 import { defineComponent, onMounted, PropType, reactive, ref } from 'vue'
export default defineComponent({ const penSize = 6
name: 'writing-board', const rubberSize = 80
props: {
color: { export default defineComponent({
type: String, name: 'writing-board',
default: '#ffcc00', props: {
}, color: {
model: { type: String,
type: String as PropType<'pen' | 'eraser'>, default: '#ffcc00',
default: 'pen', },
}, model: {
}, type: String as PropType<'pen' | 'eraser'>,
setup(props) { default: 'pen',
let ctx: CanvasRenderingContext2D | null = null },
const writingBoardRef = ref<HTMLElement>() },
const canvasRef = ref<HTMLCanvasElement>() setup(props) {
let ctx: CanvasRenderingContext2D | null = null
let lastPos = { const writingBoardRef = ref<HTMLElement>()
x: 0, const canvasRef = ref<HTMLCanvasElement>()
y: 0,
} let lastPos = {
let isMouseDown = false x: 0,
let lastTime = 0 y: 0,
let lastLineWidth = -1 }
let isMouseDown = false
// let lastTime = 0
const mouse = reactive({ let lastLineWidth = -1
x: 0,
y: 0, //
}) const mouse = reactive({
// x: 0,
const updateMousePosition = (e: MouseEvent) => { y: 0,
mouse.x = e.pageX })
mouse.y = e.pageY //
} const updateMousePosition = (e: MouseEvent | TouchEvent) => {
if (e instanceof MouseEvent) {
// mouse.x = e.pageX
const mouseInCanvas = ref(false) mouse.y = e.pageY
}
else if (e instanceof TouchEvent) {
// mouse.x = e.targetTouches[0].clientX
const initCanvas = () => { mouse.y = e.targetTouches[0].clientY
if (!canvasRef.value || !writingBoardRef.value) return }
}
ctx = canvasRef.value.getContext('2d')
if (!ctx) return //
const mouseInCanvas = ref(false)
canvasRef.value.width = writingBoardRef.value.clientWidth
canvasRef.value.height = writingBoardRef.value.clientHeight
//
canvasRef.value.style.width = writingBoardRef.value.clientWidth + 'px' const initCanvas = () => {
canvasRef.value.style.height = writingBoardRef.value.clientHeight + 'px' if (!canvasRef.value || !writingBoardRef.value) return
ctx.lineCap = 'round' ctx = canvasRef.value.getContext('2d')
ctx.lineJoin = 'round' if (!ctx) return
}
onMounted(initCanvas) canvasRef.value.width = writingBoardRef.value.clientWidth
canvasRef.value.height = writingBoardRef.value.clientHeight
//
const draw = (posX: number, posY: number, lineWidth: number) => { canvasRef.value.style.width = writingBoardRef.value.clientWidth + 'px'
if (!ctx) return canvasRef.value.style.height = writingBoardRef.value.clientHeight + 'px'
const lastPosX = lastPos.x ctx.lineCap = 'round'
const lastPosY = lastPos.y ctx.lineJoin = 'round'
}
ctx.lineWidth = lineWidth onMounted(initCanvas)
ctx.strokeStyle = props.color
ctx.beginPath() //
ctx.moveTo(lastPosX, lastPosY) const draw = (posX: number, posY: number, lineWidth: number) => {
ctx.lineTo(posX, posY) if (!ctx) return
ctx.stroke()
ctx.closePath() const lastPosX = lastPos.x
} const lastPosY = lastPos.y
// ctx.lineWidth = lineWidth
const erase = (posX: number, posY: number) => { ctx.strokeStyle = props.color
if (!ctx || !canvasRef.value) return ctx.beginPath()
const lastPosX = lastPos.x ctx.moveTo(lastPosX, lastPosY)
const lastPosY = lastPos.y ctx.lineTo(posX, posY)
ctx.stroke()
const radius = rubberSize / 2 ctx.closePath()
}
const sinRadius = radius * Math.sin(Math.atan((posY - lastPosY) / (posX - lastPosX)))
const cosRadius = radius * Math.cos(Math.atan((posY - lastPosY) / (posX - lastPosX))) //
const rectPoint1: [number, number] = [lastPosX + sinRadius, lastPosY - cosRadius] const erase = (posX: number, posY: number) => {
const rectPoint2: [number, number] = [lastPosX - sinRadius, lastPosY + cosRadius] if (!ctx || !canvasRef.value) return
const rectPoint3: [number, number] = [posX + sinRadius, posY - cosRadius] const lastPosX = lastPos.x
const rectPoint4: [number, number] = [posX - sinRadius, posY + cosRadius] const lastPosY = lastPos.y
ctx.save() const radius = rubberSize / 2
ctx.beginPath()
ctx.arc(posX, posY, radius, 0, Math.PI * 2) const sinRadius = radius * Math.sin(Math.atan((posY - lastPosY) / (posX - lastPosX)))
ctx.clip() const cosRadius = radius * Math.cos(Math.atan((posY - lastPosY) / (posX - lastPosX)))
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height) const rectPoint1: [number, number] = [lastPosX + sinRadius, lastPosY - cosRadius]
ctx.restore() const rectPoint2: [number, number] = [lastPosX - sinRadius, lastPosY + cosRadius]
const rectPoint3: [number, number] = [posX + sinRadius, posY - cosRadius]
ctx.save() const rectPoint4: [number, number] = [posX - sinRadius, posY + cosRadius]
ctx.beginPath()
ctx.moveTo(...rectPoint1) ctx.save()
ctx.lineTo(...rectPoint3) ctx.beginPath()
ctx.lineTo(...rectPoint4) ctx.arc(posX, posY, radius, 0, Math.PI * 2)
ctx.lineTo(...rectPoint2) ctx.clip()
ctx.closePath() ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
ctx.clip() ctx.restore()
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
ctx.restore() ctx.save()
} ctx.beginPath()
ctx.moveTo(...rectPoint1)
// / ctx.lineTo(...rectPoint3)
const handleMousedown = (e: MouseEvent) => { ctx.lineTo(...rectPoint4)
isMouseDown = true ctx.lineTo(...rectPoint2)
lastPos = { x: e.offsetX, y: e.offsetY } ctx.closePath()
lastTime = new Date().getTime() ctx.clip()
} ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
ctx.restore()
// }
const getDistance = (posX: number, posY: number) => {
const lastPosX = lastPos.x //
const lastPosY = lastPos.y const getDistance = (posX: number, posY: number) => {
return Math.sqrt((posX - lastPosX) * (posX - lastPosX) + (posY - lastPosY) * (posY - lastPosY)) const lastPosX = lastPos.x
} const lastPosY = lastPos.y
return Math.sqrt((posX - lastPosX) * (posX - lastPosX) + (posY - lastPosY) * (posY - lastPosY))
// st }
const getLineWidth = (s: number, t: number) => {
const maxV = 10 // st
const minV = 0.1 const getLineWidth = (s: number, t: number) => {
const maxWidth = penSize const maxV = 10
const minWidth = 3 const minV = 0.1
const v = s / t const maxWidth = penSize
let lineWidth const minWidth = 3
const v = s / t
if (v <= minV) lineWidth = maxWidth let lineWidth
else if (v >= maxV) lineWidth = minWidth
else lineWidth = maxWidth - v / maxV * maxWidth if (v <= minV) {
lineWidth = maxWidth
if (lastLineWidth === -1) return lineWidth }
return lineWidth * 1 / 3 + lastLineWidth * 2 / 3 else if (v >= maxV) {
} lineWidth = minWidth
}
// / else {
const handleMousemove = (e: MouseEvent) => { lineWidth = maxWidth - v / maxV * maxWidth
updateMousePosition(e) }
if (!isMouseDown) return if (lastLineWidth === -1) return lineWidth
return lineWidth * 1 / 3 + lastLineWidth * 2 / 3
const time = new Date().getTime() }
if (props.model === 'pen') { //
const s = getDistance(e.offsetX, e.offsetY) const handleMove = (x: number, y: number) => {
const t = time - lastTime const time = new Date().getTime()
const lineWidth = getLineWidth(s, t)
if (props.model === 'pen') {
draw(e.offsetX, e.offsetY, lineWidth) const s = getDistance(x, y)
lastLineWidth = lineWidth const t = time - lastTime
} const lineWidth = getLineWidth(s, t)
else erase(e.offsetX, e.offsetY)
draw(x, y, lineWidth)
lastPos = { x: e.offsetX, y: e.offsetY } lastLineWidth = lineWidth
lastTime = new Date().getTime() }
} else {
erase(x, y)
// / }
const handleMouseup = () => {
if (!isMouseDown) return lastPos = {x, y}
isMouseDown = false lastTime = new Date().getTime()
} }
// //
const clearCanvas = () => { const handleTouchdown = (e: TouchEvent) => {
if (!ctx || !canvasRef.value) return // mouseInCanvas.value = true
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height) isMouseDown = true
} lastPos = {x: e.targetTouches[0].clientX, y: e.targetTouches[0].clientY}
lastTime = new Date().getTime()
return { }
mouse,
mouseInCanvas, const handleTouchmove = (e: TouchEvent) => {
penSize, updateMousePosition(e)
rubberSize, if (!isMouseDown) return
writingBoardRef, handleMove(e.targetTouches[0].clientX, e.targetTouches[0].clientY)
canvasRef, }
handleMousedown,
handleMousemove, //
handleMouseup,
clearCanvas, // /
} const handleMousedown = (e: MouseEvent) => {
}, isMouseDown = true
}) lastPos = {x: e.offsetX, y: e.offsetY}
</script> lastTime = new Date().getTime()
}
<style lang="scss" scoped>
.writing-board { // /
position: fixed; const handleMousemove = (e: MouseEvent) => {
top: 0; updateMousePosition(e)
bottom: 0;
left: 0; if (!isMouseDown) return
right: 0; handleMove(e.offsetX, e.offsetY)
z-index: 8; }
cursor: none;
} // /
.eraser, .pen { const handleMouseup = () => {
pointer-events: none; if (!isMouseDown) return
position: fixed; isMouseDown = false
z-index: 9; }
.icon { //
filter: drop-shadow(2px 2px 2px #555); const clearCanvas = () => {
} if (!ctx || !canvasRef.value) return
} ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
.eraser { }
display: flex;
justify-content: center; return {
align-items: center; mouse,
border-radius: 50%; mouseInCanvas,
border: 4px solid rgba($color: #555, $alpha: .15); penSize,
color: rgba($color: #555, $alpha: .75); rubberSize,
} writingBoardRef,
canvasRef,
handleMousedown,
handleMousemove,
handleMouseup,
clearCanvas,
handleTouchdown,
handleTouchmove
}
},
})
</script>
<style lang="scss" scoped>
.writing-board {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 8;
cursor: none;
}
.eraser, .pen {
pointer-events: none;
position: fixed;
z-index: 9;
.icon {
filter: drop-shadow(2px 2px 2px #555);
}
}
.eraser {
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
border: 4px solid rgba($color: #555, $alpha: .15);
color: rgba($color: #555, $alpha: .75);
}
</style> </style>