Appearance
思路
- 一个自适应的canvas画布(图片大小就是canvas画布大小)
- 绘制图片 -> drawImage
- 蒙层 -> 半透明遮罩层
- 截图的矩形位 -> 在蒙层的上方鼠标移动的范围内进行图片在绘制
- 获取当前图片截图后的数据
- 放入另一个canvas画布中
- 保存新的canvas为图片
vue
<script setup>
import { useTemplateRef, computed, ref } from 'vue'
// 使用 useTemplateRef 获取模板中的 canvas 和 container 引用
const canvas = useTemplateRef('canvas')
const container = useTemplateRef('container')
const canvas2 = useTemplateRef('canvas2')
const container2 = useTemplateRef('canvas2Container')
const imageFile = useTemplateRef('imageFile')
// 创建一个 Image 对象用于加载和处理图像
const image = new Image()
// 初始化位置数组,用于存储鼠标按下时的初始位置
const initPos = ref([])
// 用于存储截图数据的响应式变量
let screenShotData = ref([])
// 鼠标按下的状态
const mouseDown = ref(false)
// 定义蒙层的不透明度常量
const MASK_OPACITY = 0.5
// 计算属性,获取 canvas 的 2D 绘图上下文
const ctx = computed(() => {
return canvas.value.getContext('2d')
})
const ctx2 = computed(() => {
return canvas2.value.getContext('2d')
})
/**
* 处理文件选择事件
*/
function handleFileChange(e) {
const file = e.target.files[0]
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = function (e) {
const data = e.target.result
image.src = data
image.onload = function () {
const { width, height } = image
// 根据图像尺寸生成画布,并绘制图像和蒙层
generateCanvas(container, canvas, width, height)
ctx.value.drawImage(image, 0, 0, width, height)
drwImageMask(0, 0, width, height, MASK_OPACITY)
}
}
}
/**
* 根据容器和图像尺寸生成画布
* @param {Object} container - 容器引用
* @param {Object} canvas - 画布引用
* @param {Number} width - 画布宽度
* @param {Number} height - 画布高度
*/
function generateCanvas(container, canvas, width, height) {
container.value.style.width = width + 'px'
container.value.style.height = height + 'px'
canvas.value.width = width
canvas.value.height = height
container.value.style.display = 'block'
}
/**
* 绘制图像蒙层
* @param {Number} x - 蒙层起始 x 坐标
* @param {Number} y - 蒙层起始 y 坐标
* @param {Number} width - 蒙层宽度
* @param {Number} height - 蒙层高度
* @param {Number} opacity - 蒙层不透明度
*/
function drwImageMask(x, y, width, height, opacity) {
ctx.value.fillStyle = `rgba(0,0,0,${opacity})`
ctx.value.fillRect(x, y, width, height)
}
/**
* 处理画布鼠标按下事件
*/
function handleCanvasMousedown(e) {
mouseDown.value = true
initPos.value = [e.offsetX, e.offsetY]
}
/**
* 处理画布鼠标移动事件
*/
function handleCanvasMousemove(e) {
if (mouseDown.value) {
const endX = e.offsetX
const endY = e.offsetY
const [startX, startY] = initPos.value
const reactWidth = endX - startX
const reactHeight = endY - startY
const { width, height } = canvas.value
// 更新截图数据,并重新绘制画布和蒙层
screenShotData.value = [startX, startY, reactWidth, reactHeight]
ctx.value.clearRect(0, 0, width, height)
drwImageMask(0, 0, width, height, MASK_OPACITY)
drwScreenShot(width, height, reactWidth, reactHeight)
}
}
/**
* 处理画布鼠标抬起事件
*/
function handleCanvasMouseup(e) {
mouseDown.value = false
// 根据截图数据绘制最终的截图图像
drwScreenShotImage(...screenShotData.value)
}
/**
* 绘制截图区域
* @param {Number} canWidth - 画布宽度
* @param {Number} canHeight - 画布高度
* @param {Number} reactWidth - 截图区域宽度
* @param {Number} reactHeight - 截图区域高度
*/
function drwScreenShot(canWidth, canHeight, reactWidth, reactHeight) {
ctx.value.globalCompositeOperation = 'destination-out' // 当前源外侧绘制
ctx.value.fillStyle = '#000'
ctx.value.fillRect(...initPos.value, reactWidth, reactHeight)
ctx.value.globalCompositeOperation = 'destination-over' // 当前源之上绘制
ctx.value.drawImage(image, 0, 0, canWidth, canHeight)
}
/**
* 绘制截图图像到第二个画布
* @param {Number} startX - 截图起始 x 坐标
* @param {Number} startY - 截图起始 y 坐标
* @param {Number} reactWidth - 截图宽度
* @param {Number} reactHeight - 截图高度
*/
function drwScreenShotImage(startX, startY, reactWidth, reactHeight) {
const data = ctx.value.getImageData(startX, startY, reactWidth, reactHeight)
generateCanvas(container2, canvas2, reactWidth, reactHeight)
ctx2.value.clearRect(startX, startY, reactWidth, reactHeight)
ctx2.value.putImageData(data, 0, 0)
}
</script>
<template>
<!-- 文件输入控件,用于选择图像文件 -->
<input ref="imageFile" class="mb-10" type="file" name="" accept="image/*" @change="handleFileChange" />
<!-- 主画布容器 -->
<div ref="container" class="w-full hidden border border-style-dashed border-amber">
<canvas
ref="canvas"
@mousedown="handleCanvasMousedown"
@mousemove="handleCanvasMousemove"
@mouseup="handleCanvasMouseup"
></canvas>
</div>
<!-- 截图画布容器 -->
<div ref="canvas2Container" class="w-full hidden border border-style-dashed border-amber">
<canvas ref="canvas2"></canvas>
</div>
</template>