canvas详解(2)-事件处理

208 篇文章 8 订阅
8 篇文章 1 订阅

canvas详解(2)-事件处理
上一章我们讲解了canvas的基本原理应用,这一章主要讲解一下事件如何处理。 canvas详解(1)-原理

canvas因为是画布的原因,实际上我们可以将它当做一张图片,所以在html中,无论这个canvas包含多少元素,多少图形,他都只有一个tag显示。那么它当中的图形事件也只能添加到canvas元素自身上了。

既然所有的的事件都只能添加到canvas元素上,那么怎么区分各个图形的事件呢?

根据坐标,根据每个图形在画布上不同的坐标来区分每个图形对应的事件。这就需要大量的判断以及对坐标的精确定位。canvas提供了isPointInPath(x,y)方法来判断坐标是否在当前路径上。
 

canvas的事件响应主要依赖于重绘,我们绘制的图形一般都是静态的,在很多时候会根据事件不同而呈现不同的效果,那么这些效果即是清除整个画布(clearRect()),重绘整个新的效果图。当然如果只是简单的响应,只要能精确定位坐标,能不重绘还是尽量不要重绘,毕竟重绘是相当耗时的。

以上一节的例子为例,我详细讲解下饼图鼠标移动到环形上给出提示的操作方法。
 

思路
1.事件选取:mousemove鼠标移动事件,wheel鼠标滚动事件,都添加到canvas元素上。

2.鼠标定位:必须将屏幕上鼠标的位置转化为画布上的坐标位置,这样鼠标的位置才能代表画布的位置,也才具有代表画布上某个图形的能力。

3.位置判断:鼠标放在了饼图某一部分,也就是说鼠标只在外层圆的某一部分上且不在内层圆上。

canvas的isPointInPath()只能判断指定的点是否在当前绘制路径,也就是说,我们是先画外层圆后画内层圆的,我们只能判断出点是否位于外层圆的某一个部分,但不能同时判断是否不在内层圆上。

那么我们只有先判断鼠标位置是否位于圆环上,再用isPointInPath来判断具体位于哪一个圆弧部分。

4.重绘:重绘整个效果图,当鼠标位于饼图某一部分的时候,对这一部分单独绘制,使之的外层圆半径比之前大5px,这样鼠标移入的部分就会凸显出来。

5.添加提示:在canvas的同级添加提示消息,显示饼图的相关信息。

具体实现
1.事件选取
 

canvas.addEventListener('mousemove', (e) => {
            let eventX = e.clientX  - canvas.getBoundingClientRect().left;
            let eventY = e.clientY  - canvas.getBoundingClientRect().top;
            let mousePoint = { x: eventX, y: eventY, clientX: e.clientX, clientY: e.clientY };
            let isRingRange = this.isRingPostion(mousePoint, data, this.props.nodeWidth,
                this.props.innerRadius, this.props.radius, scale);

            ctx.clearRect(0, 0, canvas.width, canvas.height);
            this.drawNode(ctx, data, data.x, data.y, this.props.nodeWidth, this.props.nodeHeight,
                this.props.radius, this.props.innerRadius, this.state.color, scale, mousePoint, isRingRange, this);
            if (!isRingRange) {
                this.setState({ tipNode: null });
            }
}, false)
 

在重绘时一定要清除画布,否则前后2次的绘制将相互重叠影响效果。

canvas.addEventListener('wheel', (e) => {
            this.setState({ tipNode: null });
 })

该事件的选取在最开始并未被考虑,但是后来发现鼠标滚动时,提示显示随之滚动,不消失,所以添加了该事件,目的是鼠标滚动时,直接关闭提示。

2.鼠标定位
事件获取的位置是相对于浏览器屏幕的位置,而我们需要的是鼠标相对于画布的位置。getBoundingClientRect()可以返回canvas相对于屏幕的位置。
 

let eventX = e.clientX  - canvas.getBoundingClientRect().left;//鼠标相对于画布的X轴坐标
let eventY = e.clientY  - canvas.getBoundingClientRect().top;//鼠标相对于画布的Y轴坐标
let mousePoint = { x: eventX, y: eventY, clientX: e.clientX, clientY: e.clientY };//记录鼠标的位置
 

3.环形区域判断

/*点击位置是否在圆环上(数据为列表)
    *mousePoint:鼠标对象位置记录
    *node:详细数据
    *nodeWidth:矩形的宽度
    *innerRadius:外层圆半径
    *radius:内层圆半径
    *scale:缩放比例
*/
isRingPostion(mousePoint, node, nodeWidth, innerRadius, radius, scale) {
        if (!mousePoint) {
            return false;
        }
        nodeWidth = nodeWidth * scale;
        innerRadius = innerRadius * scale;
        radius = radius * scale;
        let eventX = mousePoint.x;
        let eventY = mousePoint.y;
        //点击位置到圆心的距离,勾股定理计算
        let cricleX = node.x + nodeWidth / 4;//圆心x坐标
        let cricleY = node.y;
        let distanceFromCenter = Math.sqrt(Math.pow(cricleX - eventX, 2)
            + Math.pow(cricleY - eventY, 2))
        //是否在圆环上
        if (distanceFromCenter > innerRadius && distanceFromCenter < radius) {
            return true;
        }
        return false;
}
 

这里用到了2点之间的距离,鼠标位置到圆心的距离。如果该距离小于外层圆半径并且大于内层圆半径,那么该鼠标位置位于圆环内部。

需要说明一下:矩形的中心位置即是节点数据的x和y坐标,矩形被分为了左右对称的2部分,左边部分显示文字,右边部分显示圆环。而圆环的中心位置位于右边部分的中点,即是node.x + nodeWidth / 4的位置。为什么不用直接使用nodeWidth *3 / 4,因为如果存在多个节点则无法判断圆心的坐标了。

4.重绘
 

ctx.clearRect(0, 0, canvas.width, canvas.height);
this.drawNode(ctx, data, data.x, data.y, this.props.nodeWidth, this.props.nodeHeight,
                this.props.radius, this.props.innerRadius, this.state.color, scale, mousePoint, isRingRange, this);
 

5.鼠标具体位置判断

在绘制外层圆弧时判断。

if (mousePoint && ctx.isPointInPath(mousePoint.x, mousePoint.y) && isRingRange) {//鼠标点击了并且在该部分圆环上
                ctx.clearRect(-radius, -radius, 2 * radius, 2 * radius);
                this.drawDynamicPie(ctx, node, radius, color, i);//重绘圆
                let tipNode = {
                    desc: node.desc,
                    name: node.pieData[i].name,
                    value: this.floatMul(node.pieData[i].value, 100) + "%"
                }
                treePage.setState({ tipNode: tipNode, mousePosition: { x: mousePoint.clientX, y: mousePoint.clientY } });
}
 

当鼠标位置位于该部分圆弧上,且鼠标位置在圆环上时,是我们选取的环形图某部分。

在这个时候,我们对选取的部分进行单独重绘,使之凸出显示,并且记录应该出现的提示信息。

//绘制动态圆
drawDynamicPie(ctx, node, radius, color, index) {
        let startRadian = 0, endRadian = 0;
        for (let i = 0; i < node.pieData.length; i++) {
            ctx.beginPath();
            //起始点移动到圆心
            ctx.moveTo(0, 0);
            endRadian += node.pieData[i].value * Math.PI * 2;
            //以圆心为起点,0度开始绘制一个圆
            if (index == i) {
                ctx.arc(0, 0, radius + 5, startRadian, endRadian, false);
            } else {
                ctx.arc(0, 0, radius, startRadian, endRadian, false);
            }
            ctx.closePath();
            // 填充颜色
            ctx.fillStyle = color[i];
            ctx.fill();
            startRadian = endRadian;
        }
}
 

6.显示提示

提示消息显示的位置在鼠标位置偏右12px,偏下12px。但这个时候发现一个新的问题,当提示框在浏览器边缘的时候,我们的提示框被挤压的太难看了。所以我们先对提示框位置进行定位,使之不能展示的时候自动调节。

//获取提示的定位位置
getTipPosition() {
        let tipDiv = document.getElementById(`${this.props.treeId}Tip`);
        let mousePosition = this.state.mousePosition;
        let top1 = mousePosition.y + 12;
        let left = mousePosition.x + 12;
        if (tipDiv) {
            if (mousePosition.x + tipDiv.offsetWidth > window.innerWidth) {
                left = mousePosition.x - 12 - tipDiv.offsetWidth;
            }
            if (mousePosition.y + tipDiv.offsetHeight > window.innerHeight) {
                top1 = mousePosition.y - 12 - tipDiv.offsetHeight;
            }
        }
        return { top: top1, left: left }
}
 

这里用到提示框自身的宽高了,所以我们不能用display:none,它将不能获取提示框的宽高,而visibility:hidden即使在隐藏的时候也占有位置,可以被我们利用。

render() {
        let position = this.getTipPosition();
        let tipClass = {
            position: 'fixed',
            zIndex: 999,
            visibility: this.state.tipNode ? 'visible' : 'hidden',
            backgroundColor: '#826d6d',
            top: position.top,
            left: position.left,
            padding: '15px',
            color: '#fff',
            borderRadius: '5px',
            textAlign: 'left'
        }
        return (
            <div style={{ padding: 100 }}>
                <canvas id={this.props.canvasId} width={this.props.width} height={this.props.height} style={{ zoom: this.state.ratio }}></canvas>
                <div style={tipClass} id={`${this.props.treeId}Tip`}>
                    <div>{this.state.tipNode ? this.state.tipNode.desc : null}</div>
                    <div>{this.state.tipNode ? this.state.tipNode.name : null} : {this.state.tipNode ? this.state.tipNode.value : null}</div>
                </div>
            </div>
        );
    }
 
 

转自https://blog.csdn.net/mafan121/article/details/81625592

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Canvas 是 HTML5 中的一个元素,它提供了一种在网页上绘制图形、动画和视频的方法。要回执图像和视频,可以使用 Canvas 的 drawImage 方法。 要回执图像,首先需要创建一个 Image 对象,并将图像的路径指定给它。然后,可以使用 Canvas 的 drawImage 方法将图像绘制到 Canvas 上。这个方法接受几个参数,包括图像对象、图像的起始坐标以及可选的宽度和高度。 下面是一个回执图像的示例: ```javascript var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); var img = new Image(); img.onload = function() { ctx.drawImage(img, 0, 0, canvas.width, canvas.height); }; img.src = "path/to/image.jpg"; ``` 要回执视频,可以使用 HTML5 的 video 元素。首先,创建一个 video 元素,并将视频的路径指定给它。然后,可以使用 Canvas 的 drawImage 方法将视频绘制到 Canvas 上。与回执图像类似,这个方法接受几个参数,包括视频元素、视频的起始坐标以及可选的宽度和高度。 下面是一个回执视频的示例: ```javascript var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); var video = document.getElementById("myVideo"); video.addEventListener("play", function() { setInterval(function() { ctx.drawImage(video, 0, 0, canvas.width, canvas.height); }, 1000 / 30); }, false); video.src = "path/to/video.mp4"; ``` 这些示例代码演示了如何使用 Canvas 回执图像和视频。你可以根据自己的需求进行调整和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值