Canvas鼠标滚轮缩放以及画布拖动(图文并茂版)
Canvas鼠标滚轮缩放以及画布拖动
本文会带大家认识Canvas中常用的坐标变换方法 translate 和 scale,并结合这两个方法,实现鼠标滚轮缩放以及画布拖动功能。
Canvas的坐标变换
Canvas 绘图的缩放以及画布拖动主要通过 CanvasRenderingContext2D 提供的 translate 和 scale 两个方法实现的,先来认识下这两个方法。
translate 方法
语法:
translate(x, y)
translate 的用法记住一句话:
translate 方法重新映射画布上的(0, 0)位置。
说白了就是把画布的原点移动到了 translate 方法指定的坐标,之后所有图形的绘制都会以该坐标进行参照。
举个例子:
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 600;
canvas.height = 400;ctx.fillStyle = 'red';
ctx.fillRect(50, 50, 50, 50);ctx.translate(50, 50);ctx.fillStyle = 'green';
ctx.fillRect(50, 50, 50, 50);
开始的时候,Canvas 容器原点和绘图原点重合,绘制一个背景色为红色,原点坐标(50, 50),长宽各为 50 的矩形,接着调用 translate 方法将绘图原点沿水平和纵向各偏移50,再绘制一个背景色是绿色,原点坐标(50, 50),长宽各为 50 的矩形,示意图如下,其中灰色的背景为 Canvas 区域。

需要注意的是,如果此时继续调用 translate 方法进行偏移操作,后续的偏移会基于原来偏移的基础上进行的。
ctx.fillStyle = 'red';
ctx.fillRect(50, 50, 50, 50);// 第一次坐标系偏移
ctx.translate(50, 50);ctx.fillStyle = 'green';
ctx.fillRect(50, 50, 50, 50);// 第二次坐标系偏移
ctx.translate(50, 50);ctx.fillStyle = 'blue';
ctx.fillRect(50, 50, 50, 50);

因此,如果涉及到多次调用 translate 方法进行坐标变换,很容易将坐标系搞混乱,所以,一般在translate 之前会调用 save 方法先保存下绘图的状态,再调用 translate 后,绘制完图形后,调用 restore 方法恢复之前的上下文,对坐标系进行还原,这样不容易搞乱坐标系。
save方法通过将当前状态压入堆栈来保存画布的整个状态。
保存到堆栈上的图形状态包括:
- 当前转换矩阵。
- 当前裁剪区域。
- 当前的破折号列表。
- 包含的属性:strokeStyle、ill Style、lobalAlpha、linewidth、lineCap、lineJoin、miterLimit、lineDashOffset、shadowOffsetX、shadowOffsetY、shadowBlur、shadowColor、global alCompositeOperation、Font、extAlign、extBaseline、Direction、ImageSmoothingEnabled。
restore 方法通过弹出绘制状态堆栈中的顶部条目来恢复最近保存的画布状态。
ctx.fillStyle = 'red';
ctx.fillRect(50, 50, 50, 50);// 保存绘图上下文
ctx.save()ctx.translate(50, 50);
ctx.fillStyle = 'green';
ctx.fillRect(50, 50, 50, 50);// 绘制完成后恢复上下文
ctx.restore()ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 50, 50);

scale 方法
语法:
scale(x, y)
缩放 (scale) 就是将一个图形围绕中心点,然后将宽和高分别乘以一定的因子(sx,sy)
默认情况下,画布上的一个单位正好是一个像素。缩放变换会修改此行为。例如,如果比例因子为0.5,则单位大小为0.5像素;因此,形状的绘制大小为正常大小的一半。类似地,比例因子为2会增加单位大小,使一个单位变为两个像素;从而以正常大小的两倍绘制形状。
举个例子:
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');ctx.scale(0.5,2);
ctx.fillStyle="blue";
ctx.fillRect(50,50,100,50);
调用 scale(0.5,2) 将画布水平方向缩小一倍,垂直方向放大一倍,绘制一个坐标原点 (50, 50),宽度 100,高度 50 的矩形。经过缩放变换后,距离原点的实际像素是横轴 25像素,纵轴 100 像素,宽度 50 像素,高度 100 像素。

实现鼠标拖动画布
效果
创建Sence类
Sence类:
class Scene {constructor(id, options = {width: 600,height: 400}) {this.canvas = document.querySelector('#' + id)this.width = options.width;this.height = options.height;this.canvas.width = options.width;this.canvas.height = options.height;this.ctx = this.canvas.getContext('2d');}draw() {this.ctx.fillStyle = 'red';this.ctx.fillRect(50, 50, 50, 50);this.ctx.fillStyle = 'green';this.ctx.fillRect(150, 150, 50, 50);}clear() {this.canvas.width = this.width;}paint() {this.clear();this.draw();}
}let scene = new Scene('canvas');scene.draw();
在 Sence 类的构造函数中初始化 Canvas,得到 CanvasRenderingContext2D 对象,并设置 Canvas 的宽高属性,draw 方法里面绘制了两个矩形。
在进行下面的工作之前,我们先来了解下 Canvas 的事件机制。
通过 addEventListener 方法可以给 Canvas 绑定一个事件。
this.canvas.addEventListener('mousedown', (event) => {console.log(event.x)
});
事件的回调函数参数的 event 对象中可以获取鼠标点击 Canvas 时的坐标信息,event 对象中经常会用到的坐标有两个,一个是 event.x 和 event.y,另一个是 event.offsetX 和 event.offsetY,其中,event.x 和 event.y 获取的是鼠标点击时相对于屏幕的坐标,而 event.offsetX 和 event.offsetY 是相对于 Canvas 容器的坐标。
通过下面这张图可以清晰的看出两个坐标的区别,明白这一点对于我们后续的坐标变换非常重要。

在构造函数中添加对 Canvas 的 mousedown 事件监听,记录点击鼠标时相对屏幕的位置 x 和 y。
class Scene {x = 0; // 记录鼠标点击Canvas时的横坐标y = 0; // 记录鼠标点击Canvas时的纵坐标constructor(id, options = {width: 600,height: 400}) {this.canvas.addEventListener('mousedown', this.onMousedown);}onMousedown(e) {if (e.button === 0) {// 点击了鼠标左键this.x = x;this.y = y;}}
}
画布拖动的整体思路就是利用前面介绍的 Canvas 的 translate 方法。画布的整体偏移量记录在 offset.x 和 offset.y,鼠标触发 mousedown 事件时,记录当前鼠标点击的位置相对于屏幕的坐标 x, 和 y,并且开始监听鼠标的 mousemove 和 mouseup 事件。鼠标触发 mousemove 事件时计算每次移动时整体累加的偏移量:
onMousemove(e) {this.offset.x = this.curOffset.x + (e.x - this.x);this.offset.y = this.curOffset.y + (e.y - this.y);this.paint();
}
其中 curOffset.x 和 curOffset.y 记录的是鼠标触发 mouseup 时保存的当前的偏移量,便于计算累加的偏移量。每次触发完鼠标 mousemove 事件后,重新进行图形绘制。
onMouseup() {this.curOffset.x = this.offset.x;this.curOffset.y = this.offset.y;window.removeEventListener('mousemove', this.onMousemove);window.removeEventListener('mouseup', this.onMouseup);
}
Sence 类完整代码如下:
class Scene {offset = { x: 0, y: 0 }; // 拖动偏移curOffset = { x: 0, y: 0 }; // 记录上一次的偏移量x = 0; // 记录鼠标点击Canvas时的横坐标y = 0; // 记录鼠标点击Canvas时的纵坐标constructor(id, options = {width: 600,height: 400}) {this.canvas = document.querySelector('#' + id);this.width = options.width;this.height = options.height;this.canvas.width = options.width;this.canvas.height = options.height;this.ctx = this.canvas.getContext('2d');this.onMousedown = this.onMousedown.bind(this);this.onMousemove = this.onMousemove.bind(this);this.onMouseup = this.onMouseup.bind(this);this.canvas.addEventListener('mousedown', this.onMousedown);}onMousedown(e) {if (e.button === 0) {// 鼠标左键this.x = e.x;this.y = e.ywindow.addEventListener('mousemove', this.onMousemove);window.addEventListener('mouseup', this.onMouseup);}}onMousemove(e) {this.offset.x = this.curOffset.x + (e.x - this.x);this.offset.y = this.curOffset.y + (e.y - this.y);this.paint();}onMouseup() {this.curOffset.x = this.offset.x;this.curOffset.y = this.offset.y;window.removeEventListener('mousemove', this.onMousemove);window.removeEventListener('mouseup', this.onMouseup);}draw() {this.ctx.fillStyle = 'red';this.ctx.fillRect(50, 50, 50, 50);this.ctx.fillStyle = 'green';this.ctx.fillRect(150, 150, 50, 50);}clear() {this.canvas.width = this.width;}paint() {this.clear();this.ctx.translate(this.offset.x, this.offset.y);this.draw();}
}
上述代码中有几点需要注意:
- 事件函数中的this指向问题
细心的同学可能注意到,在 Sence 类的构造函数里有这样几行代码:
constructor(id, options = {width: 600,height: 400}) {this.onMousedown = this.onMousedown.bind(this);this.onMousemove = this.onMousemove.bind(this);this.onMouseup = this.onMouseup.bind(this);}
为什么要使用 bind 函数给事件函数重新绑定this对象呢?
主要的原因在于一个事件有监听就会有移除。假设我们想要销毁 mousemove 事件怎么办呢?
可以调用 removeEventListener 方法进行事件监听的移除,比如上述代码会在 onMouseup 中移除对 mousemove 事件的监听:
onMouseup() {this.curOffset.x = this.offset.x;this.curOffset.y = this.offset.y;window.removeEventListener('mousemove', this.onMousemove);
}
如果不在构造函数中使用 bind 方法重新绑定 this 指向,此时的 this 指向的就是window,因为 this 指向的是调用 onMouseup 的对象,而 onMouseup 方法是被 window 上的 mouseup 事件调用的,但是实际上我们想要的this指向应该 Sence 实例。为了避免上述问题的出现,最好的解决办法就是在 Sence 类的构造函数中重新绑定 this 指向。
- 画布的清空问题
每次鼠标移动的时候会改变 Canvas 的 CanvasRenderingContext2D 偏移量,并重新进行图形的绘制,重新绘制的过程就是先将画布清空,然后设置画布的偏移量(调用 translate 方法),接着绘制图形。其中清空画布这里选择了重新设置Canvas的宽度,而不是调用 clearRect 方法,主要是因为clearRect 方法只在 Canvas 的渲染上下文没有进行过平移、缩放、旋转等变换时有效,如果 Canvas 的渲染上下文已经经过了变换,那么在使用 clearRect 清空画布前,需要先重置变换,否则 clearRect 将无法有效地清除整块画布。
实现鼠标滚轮缩放
效果
实现原理
鼠标滚轮的放大需要结合上面介绍的 Canvas 的 translate 和 scale 两个方法进行组合变换。
计算放大系数
监听鼠标滚轮的 mousewheel 事件,在事件的回调函数中通过 event.wheelDelta 值的变化来实时计算当前的缩放值,其中 event.wheelDelta > 0 表示放大,反之表示缩小,放大和缩小都有对应的阈值,超过阈值就禁止继续放大和缩小。
改造 Sence 类,添加 onMousewheel 事件:
onMousewheel(e) {if (e.wheelDelta > 0) {// 放大this.scale = parseFloat((this.scaleStep + this.scale).toFixed(2)); // 解决小数点运算丢失精度的问题if (this.scale > this.maxScale) {this.scale = this.maxScale;return;}} else {// 缩小this.scale = parseFloat((this.scale - this.scaleStep).toFixed(2)); // 解决小数点运算丢失精度的问题if (this.scale < this.minScale) {this.scale = this.minScale;return;}}this.preScale = this.scale;
}
其中,this.scale / this.preScale 计算出来的值就是放大系数,暂且记做 n。
在计算放大系数的时候,需要注意两个浮点型数值在计算不能直接相加,否则会出现丢失精度的问题。
缩放原理
在缩放的时候,会调用 scale(n, n) 方法,将坐标系放大 n 倍。假设鼠标滚轮停在 A 点进行放大操作,放大之后得到坐标 A’ 点。

可以看到,放大之后,A(x1, y1) 坐标变换到了 A'(x1, y1),A => A' 放大了 n 倍,因此得到 x1 = x * n,y1 = y1 * n。
这个时候就会存在一个问题,我们在 A 点进行放大,放大后得到的 A' 的位置应该是不变的,所以需要在放大之后需要调整 A’ 点的位置到 A 点。
这里我们采用的策略是在放大前先偏移一段距离,然后进行放大之后就可以保持 A 点和 A‘ 点的重合。

鼠标停留在 A 点对蓝色矩形进行放大,放大系数为 n,蓝色矩形的起点左上角和坐标原点重合,宽度和高度分别是 x 和 y,因此,A点的坐标为 (x, y)。
前面我们说过,对 A 点进行放大后得到的 A’点应该和A点重合,这样就需要先把整个坐标系沿着x轴和y轴分别向左和向上偏移 offsetX 和 offsetY,偏移后得到的 A'点坐标记作 (x1, x2),因为 A 点是经过放大 n 倍后得到的 A' 点,所以得到以下距离关系:
x1 = x * n;
y1 = y * n
进一步就可以得到横纵坐标的偏移量 offsetX 和 offsetY 的绝对值:
offsetX = x*n-x;
offsetY =x*n - y;
因此,这需要将坐标系经过 translate(-offsetX, -offsetY) 之后,再 scale(n, n),就能确保 A 点 和 A‘ 点重合了。
明白了缩放的基本原理,下面就继续码代码吧😜。
onMousewheel(e) {e.preventDefault();this.mousePosition.x = e.offsetX; // 记录当前鼠标点击的横坐标this.mousePosition.y = e.offsetY; // 记录当前鼠标点击的纵坐标if (e.wheelDelta > 0) {// 放大this.scale = parseFloat((this.scaleStep + this.scale).toFixed(2)); // 解决小数点运算丢失精度的问题if (this.scale > this.maxScale) {this.scale = this.maxScale;return;}} else {// 缩小this.scale = parseFloat((this.scale - this.scaleStep).toFixed(2)); // 解决小数点运算丢失精度的问题if (this.scale < this.minScale) {this.scale = this.minScale;return;}}this.offset.x = this.mousePosition.x - ((this.mousePosition.x - this.offset.x) * this.scale) / this.preScale;this.offset.y = this.mousePosition.y - ((this.mousePosition.y - this.offset.y) * this.scale) / this.preScale;this.paint(this.ctx);this.preScale = this.scale;this.curOffset.x = this.offset.x;this.curOffset.y = this.offset.y;
}paint() {this.clear();this.ctx.translate(this.offset.x, this.offset.y);this.ctx.scale(this.scale, this.scale);this.draw();
}
总结
本文从基础原理到代码实现,完整给大家讲解了 Canvas 画布绘制中经常会遇到的画布拖动和鼠标滚轮缩放功能,希望对大家有帮助,更多精彩文章欢迎大家关注我的vx公众号:前端架构师笔记。本文完整代码地址:https://github.com/astonishqft/scanvas-translate-and-scale
相关文章:
Canvas鼠标滚轮缩放以及画布拖动(图文并茂版)
Canvas鼠标滚轮缩放以及画布拖动 本文会带大家认识Canvas中常用的坐标变换方法 translate 和 scale,并结合这两个方法,实现鼠标滚轮缩放以及画布拖动功能。 Canvas的坐标变换 Canvas 绘图的缩放以及画布拖动主要通过 CanvasRenderingContext2D 提供的 …...
[ECCV 2020] FGVC via progressive multi-granularity training of jigsaw patches
Contents IntroductionProgressive Multi-Granularity (PMG) training frameworkExperimentsReferencesIntroduction 不同于显式地寻找特征显著区域并抽取其特征,作者充分利用了 CNN 不同 stage 输出的特征图的语义粒度信息,并使用 Jigsaw Puzzle Generator 进行数据增强来帮…...
Python推导式
列表(list)推导式 [remove for source in xx_list]或者[remove for source in xx_list if condition] 实例: names[Bob,Mark,Mausk,Johndan,Wendy] new_names[name.upper() for name in names if len(name)<5] print(new_names)即迭代列…...
Java列表List的定查改增删操作
Java列表List的定查改增删操作定义查找遍历元素与下标互查修改增加删除java.util中提供了三种常用的集合类,列表List、集合Map和字典Set。这些集合类相较于数组有更多功能,并且都可以通过Iterator(迭代器)来访问。 在这篇博客中&…...
day03java语言特性 JDK、JRE、JVM
1、Java语言的特性 1.1、简单性在Java语言当中真正操作内存的是:JVM(Java虚拟机)所有的java程序都是运行在Java虚拟机当中的。而Java虚拟机执行过程中再去操作内存。对于C或者C来说程序员都是可以直接通过指针操作内存的。C或者C更灵活&…...
HydroD 实用教程(二)有限元模型
目 录一、前言二、模型种类三、单元类型四、FEM文件五、参考文献一、前言 SESAM (Super Element Structure Analysis Module)是由挪威船级社(DNV-GL)开发的一款有限元分析(FEA)系统,它以 GeniE、…...
Java中的Set集合
Set不能存储重复元素,元素无序(指的是不按照添加的顺序,List集合是按照添加顺序存储的)hashSet注:源码底层是hashMap实现的,因为hashMap是双列的,其中键是不能重复的,而hashSet是单列…...
【RabbitMQ五】——RabbitMQ路由模式(Routing)
RabbitMQ路由模式前言RabbitMQ模式的基本概念为什么要使用Rabbitmq 路由模式RabbitMQ路由模式组成元素路由模式完整代码Pom文件引入RabbtiMQ依赖RabbitMQ工具类生产者消费者1消费者2运行结果截图前言 通过本篇博客能够简单使用RabbitMQ的路由模式。 本篇博客主要是博主通过官网…...
【C语言】宏定义 结构体 枚举变量的用法
目录 一、数据类型 二、C语言宏定义 三、C语言typedef重命名 四、 #define与typedef的区别 五、结构体 六、枚举变量 补充学习一点STM32的必备基础知识 一、数据类型 二、C语言宏定义 关键字:#define 用途:用一个字符串代替一个数字,…...
锁升级之Synchronized
Synchronized JVM系统锁一个对象里如果有多个synchronized方法,同一时刻,只要有一个线程去调用其中的一个synchronized方法,其他线程只能等待!锁的是当前对象,对象被锁定后,其他线程都不能访问当前对象的其…...
基于nodejs+vue疫情网课管理系统
疫情网课也都将通过计算机进行整体智能化操作,对于疫情网课管理系统所牵扯的管理及数据保存都是非常多的,例如管理员:首页、个人中心、学生管理、教师管理、班级管理、课程分类管理、课程表管理、课程信息管理、作业信息管理、请假信息管理、上课签到管理、论坛交流…...
Zabbix 构建监控告警平台(三)
Zabbix User parametersZabbix Trigger1.Zabbix User parameters 1.1即自定义KEY 注意:mysql安装在被监测主机 [rootlocalhost ~]# yum -y install mariadb-server mariadb [rootlocalhost ~]# systemctl start mariadb [rootlocalhost ~]# mysqladmin -uroot statu…...
Linux系统之dool命令行工具的基本使用
Linux系统之dool命令行工具的基本使用一、dool命令行工具介绍二、本地系统环境检查1.检查系统版本2.检查系统内核版本三、下载dool软件包1.创建下载目录2.下载dool四、安装dool1.安装python32.安装dool五、dool的命令帮助六、dool的基本使用1.直接使用dool监控系统2.监控cpu和网…...
LeetCode-2335-装满杯子需要的最短总时长
1、堆 我们可以维护一个堆,首先我们将数组中不为0的数全部加入堆中,而后进行循环。当堆不为空时,我们将堆顶元素出堆并减一,而后观察是否还能继续出堆,若能则出堆,否则跳过,最后我们将处理后的…...
npm ERR! code ELIFECYCLE解决方案,npm犯错!myweb@1.0.0构建脚本失败。
1.问题npm ERR! code ELIFECYCLEnpm ERR! errno 1npm ERR! myweb1.0.0 build: webpack --config config/webpack.config.jsnpm ERR! Exit status 1npm ERR!npm ERR! Failed at the myweb1.0.0 build script.npm犯错!代码ELIFECYCLEnpm犯错!errno 1npm犯错!myweb1.0.0 build: we…...
最小二乘支持向量机”在学习偏微分方程 (PDE) 解方面的应用(Matlab代码实现)
目录 💥1 概述 📚2 运行结果 🎉3 参考文献 👨💻4 Matlab代码 💥1 概述 本代码说明了“最小二乘支持向量机”在学习偏微分方程 (PDE) 解方面的应用。提供了一个示例,…...
ISYSTEM调试实践8-winIDEA Analyzer功能1
前面几篇介绍了ISYSTEM的基本调试界面和功能,相比我之前用过的IDE,除了几种断点方式和脚本功能以外,应该都是比较简单,稍微操作一下就可以直接上手,后续我将介绍winIDEA的Analyzer 功能。 1 Analyzer简介 iSYSTEM An…...
每日学术速递2.11
CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.IR、cs.MM 1.A Comprehensive Survey on Multimodal Recommender Systems: Taxonomy, Evaluation, and Future Directions 标题:关于多模态推荐系统的综合调查:分…...
宝塔搭建实战php开源likeadmin通用管理admin端vue3源码(二)
大家好啊,我是测评君,欢迎来到web测评。 上一期给大家分享了server端的部署方式,今天来给大家分享admin端在本地搭建,与打包发布到宝塔的方法。感兴趣的朋友可以自行下载学习。 技术架构 vscode node16 vue3 elementPlus vit…...
网络基础-虚拟化工具-网桥
系列文章目录 本系列文章主要是回顾和学习工作中常用的网络基础命令,在此记录以便于回顾。 该篇文章主要是讲解虚拟化的工具网桥相关的概念和常用命令 提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录系…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...
Python网页自动化Selenium中文文档
1. 安装 1.1. 安装 Selenium Python bindings 提供了一个简单的API,让你使用Selenium WebDriver来编写功能/校验测试。 通过Selenium Python的API,你可以非常直观的使用Selenium WebDriver的所有功能。 Selenium Python bindings 使用非常简洁方便的A…...
