canvas放大镜
原理
首先选择图片的一块区域,而后将这块区域放大,而后再绘制到原先的图片上,保证两块区域的中心点一致;
- 初始化:
一个canvas, 预加载image(也可以通过html预加载), 得到 canvas 和 image 对象
设置相关变量
<canvas ref="myCanvas"></canvas>
<!-- <img src="image.png" style="display: none" id="img">-->
import img from '@/assets/a.webp';
// 相关变量
interface basicSetType {
// 图片被放大区域的中心点,也是放大镜的中心点
centerPoint: centerPointType;
// 图片被放大区域的半径
originalRadius: number;
// 图片被放大区域
originalRectangle: originalRectangleType;
// 放大倍数
scale: number;
// 放大后区域
scaleGlassRectangle: scaleGlassRectangleType;
}
- 画背景图
- 计算图片被放大的区域的范围
使用鼠标的位置做为被放大区域的中心点(放大镜随着鼠标移动而移动),由于 canvas 在画图片的时候,须要知道左上角的坐标以及区域的宽高,因此这里咱们计算区域的范围
// 计算图片被放大的区域的范围
const calOriginalRectangle = () => {
const radius = basicSet.originalRadius / 2;
const {x, y} = basicSet.centerPoint;
basicSet.originalRectangle = {
x: x - radius,
y: y - radius,
width: basicSet.originalRadius,
height: basicSet.originalRadius
}
}
- 计算放大镜区域
经过中心点、被放大区域的宽高以及放大倍数,得到区域的左上角坐标以及区域的宽高。 - 绘制放大镜区域
裁剪区域
放大镜通常是圆形的,这里咱们使用 clip 函数裁剪出一个圆形区域,而后在该区域中绘制放大后的图。一旦裁减了某个区域,之后全部的绘图都会被限制的这个区域里,这里咱们使用 save 和 restore 方法清除裁剪区域的影响。save 保存当前画布的一次状态,包含 canvas 的上下文属性,例如 style,lineWidth 等,而后会将这个状态压入一个堆栈。restore 用来恢复上一次 save 的状态,从堆栈里弹出最顶层的状态。 - 绘制图片
在这里咱们使用 context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height); 方法,将 canvas 自身做为一副图片,而后取被放大区域的图像,将其绘制到放大镜区域里。 - 绘制放大边缘
createRadialGradient 用来绘制渐变图像 - 绑定事件
在按下时判断鼠标位置是否在canvas画布中,在画布中再绑定鼠标移动事件去绘图。鼠标松开时清除事件并重辉背景。
注: 由于鼠标移动绘制放大区域,需要每一次绘制重画背景使背景图不发生变化。
// 绘制放大区域
const drawScaleRectangle = (myCanvas: Ref) => {
const ctx = myCanvas.value.getContext('2d');
// 每次画之前重画图片
ctx.drawImage(image, 0, 0, 400, 400)
ctx.save();
ctx.beginPath();
ctx.arc(basicSet.centerPoint.x, basicSet.centerPoint.y, basicSet.originalRadius, 0, Math.PI*2, false);
ctx.clip();
const oR = basicSet.originalRectangle;
const sR = basicSet.scaleGlassRectangle;
drawScaleGradient(ctx);
// 绘制放大图片
ctx.drawImage(myCanvas.value, oR.x, oR.y, oR.width, oR.height, sR.x, sR.y, sR.width, sR.height);
ctx.restore();
}
代码
<template>
<div class="box">
<canvas ref="myCanvas" class="pic" width="400" height="400" v-on:="{mousedown: mouseInArea, mouseup: clearScaleRectangle}"></canvas>
</div>
</template>
<script lang="ts">
import img from '@/assets/a.webp'
</script>
<script setup lang="ts">
import { ref, onMounted, Ref, reactive } from 'vue'
const myCanvas = ref<HTMLCanvasElement>()
interface centerPointType {
x: number;
y: number;
}
interface originalRectangleType {
x: number;
y: number;
width: number;
height: number;
}
interface scaleGlassRectangleType {
x: number;
y: number;
width: number;
height: number;
}
interface basicSetType {
// 图片被放大区域的中心点,也是放大镜的中心点
centerPoint: centerPointType;
// 图片被放大区域的半径
originalRadius: number;
// 图片被放大区域
originalRectangle: originalRectangleType;
// 放大倍数
scale: number;
// 放大后区域
scaleGlassRectangle: scaleGlassRectangleType;
}
const basicSet: basicSetType = reactive({
// 图片被放大区域的中心点,也是放大镜的中心点
centerPoint: { x: 0, y: 0},
// 图片被放大区域的半径
originalRadius: 100,
// 图片被放大区域
originalRectangle: {x: 0, y: 0, width: 0, height: 0},
// 放大倍数
scale: 2,
// 放大后区域
scaleGlassRectangle: {x: 0, y: 0, width: 0, height: 0},
});
const image = new Image()
image.src = img;
// 获取鼠标移动的中心
const catchMouseCenter = (event: MouseEvent) => {
// 鼠标事件得到坐标通常为屏幕的或者 window 的坐标,咱们须要将其装换为 canvas 的坐标
// 用于得到页面中某个元素的左,上,右和下分别相对浏览器视窗的位置
const rect = myCanvas.value?.getBoundingClientRect() || {left: 0, top: 0};
basicSet.centerPoint.x = event.x - rect.left;
basicSet.centerPoint.y = event.y - rect.top;
computeDrawBigGlass();
}
const computeDrawBigGlass = () => {
calOriginalRectangle();
scaleGlassRectangle();
drawScaleRectangle(myCanvas);
}
// 计算图片被放大的区域的范围
const calOriginalRectangle = () => {
const {x, y} = basicSet.centerPoint;
basicSet.originalRectangle = {
x: x - basicSet.originalRadius,
y: y - basicSet.originalRadius,
width: basicSet.originalRadius*2,
height: basicSet.originalRadius*2
}
}
// 计算放大镜区域
const scaleGlassRectangle = () => {
const radius = basicSet.originalRadius;
const scale = basicSet.scale;
const {x, y} = basicSet.centerPoint;
basicSet.scaleGlassRectangle = {
x: x - radius*scale,
y: y - radius*scale,
width: radius*scale*2,
height: radius*scale*2
}
}
// 绘制放大区域
const drawScaleRectangle = (myCanvas: Ref) => {
const ctx = myCanvas.value.getContext('2d');
// 每次画之前重画图片
ctx.drawImage(image, 0, 0, 400, 400)
ctx.save();
ctx.beginPath();
ctx.arc(basicSet.centerPoint.x, basicSet.centerPoint.y, basicSet.originalRadius, 0, Math.PI*2, false);
ctx.clip();
const oR = basicSet.originalRectangle;
const sR = basicSet.scaleGlassRectangle;
drawScaleGradient(ctx);
// 绘制放大图片
ctx.drawImage(myCanvas.value, oR.x, oR.y, oR.width, oR.height, sR.x, sR.y, sR.width, sR.height);
ctx.restore();
}
// 绘制放大边缘
const drawScaleGradient = (ctx: CanvasRenderingContext2D) => {
ctx.beginPath();
// 渐变图像
const {x, y} = basicSet.centerPoint;
const radius = basicSet.originalRadius;
const gradient = ctx.createRadialGradient(x, y, radius - 5, x, y, radius);
gradient.addColorStop(0, 'rgba(0,0,0,0.2)');
gradient.addColorStop(0.80, 'silver');
gradient.addColorStop(0.90, 'silver');
gradient.addColorStop(1.0, 'rgba(150,150,150,0.9)');
ctx.strokeStyle = gradient;
ctx.lineWidth = 4;
ctx.arc(x, y, radius, 0, Math.PI * 2, false);
ctx.stroke();
}
// 清除绘图
const clearScaleRectangle = () => {
const ctx = myCanvas.value?.getContext('2d');
ctx?.drawImage(image, 0, 0, 400, 400)
myCanvas.value?.removeEventListener('mousemove', catchMouseCenter);
myCanvas.value?.removeEventListener('mousewheel', scaleControl);
myCanvas.value?.removeEventListener('DOMMouseScroll', scaleControl);
}
// 判断鼠标的位置
const mouseInArea = (event: MouseEvent) => {
const rect = myCanvas.value?.getBoundingClientRect();
if ((rect?.top || 0) < event.y && (rect?.left || 0) < event.x) {
catchMouseCenter(event);
if (myCanvas.value) {
myCanvas.value.addEventListener('mousemove', catchMouseCenter);
myCanvas.value.addEventListener('mousewheel', scaleControl);
// // firefox
myCanvas.value.addEventListener('DOMMouseScroll', scaleControl);
};
}
}
// 鼠标控制放大限度
const scaleControl = (event: WheelEvent) => {
if (event.detail) {
basicSet.scale = event.detail > basicSet.scale ? event.detail : basicSet.scale;
} else {
basicSet.scale = event.deltaY > 0 ? 4 : 2;
}
computeDrawBigGlass();
}
// 背景图片
const drawBackGround = (myCanvas: Ref) => {
const ctx = myCanvas.value.getContext('2d')
image.onload = () => {
ctx.drawImage(image, 0, 0, 400, 400)
}
}
onMounted(() => {
drawBackGround(myCanvas);
})
</script>
<style lang="scss">
.box {
position: relative;
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
}
.pic {
display: block;
margin: 0 auto;
cursor: crosshair;
}
</style>
TS上WheelEvent
TypeScript 3.2
wheelDelta 和它的小伙伴们被移除了。
wheelDeltaX、wheelDelta 和 wheelDeltaZ 全都被移除了,因为他们在 WheelEvents 上是废弃的属性。
解决办法:使用 deltaX、deltaY 和 deltaZ 代替。
实验结果是deltaX:-0; deltaZ: 0;deltaY: -100, 100.