Canvas

本文介绍了使用HTML5 Canvas创建放大镜效果的原理和步骤,包括计算放大区域、绘制放大镜、处理鼠标事件等,并提供了关于TypeScript中处理WheelEvent的注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

canvas放大镜

原理
首先选择图片的一块区域,而后将这块区域放大,而后再绘制到原先的图片上,保证两块区域的中心点一致;

  1. 初始化:
    一个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;
    }
  1. 画背景图
  2. 计算图片被放大的区域的范围
    使用鼠标的位置做为被放大区域的中心点(放大镜随着鼠标移动而移动),由于 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
        }
    }
  1. 计算放大镜区域
    经过中心点、被放大区域的宽高以及放大倍数,得到区域的左上角坐标以及区域的宽高。
  2. 绘制放大镜区域
    裁剪区域
    放大镜通常是圆形的,这里咱们使用 clip 函数裁剪出一个圆形区域,而后在该区域中绘制放大后的图。一旦裁减了某个区域,之后全部的绘图都会被限制的这个区域里,这里咱们使用 save 和 restore 方法清除裁剪区域的影响。save 保存当前画布的一次状态,包含 canvas 的上下文属性,例如 style,lineWidth 等,而后会将这个状态压入一个堆栈。restore 用来恢复上一次 save 的状态,从堆栈里弹出最顶层的状态。
  3. 绘制图片
    在这里咱们使用 context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height); 方法,将 canvas 自身做为一副图片,而后取被放大区域的图像,将其绘制到放大镜区域里。
  4. 绘制放大边缘
    createRadialGradient 用来绘制渐变图像
  5. 绑定事件
    在按下时判断鼠标位置是否在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.

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值