理解 HTML5 Canvas 中逻辑像素与物理像素的关系

理解 HTML5 Canvas 中逻辑像素与物理像素的关系

在使用 HTML5 Canvas 时,开发者经常会遇到一个困惑:为什么鼠标的 offsetXoffsetY 和我绘制的图形坐标对不上?这通常是因为 Canvas 的逻辑像素大小和物理像素大小不一致。本文将详细解释这个问题,并给出通用解决方案。


什么是逻辑像素和物理像素?

逻辑像素

  • 是 Canvas 内部的绘图坐标系大小,由 Canvas 元素的 widthheight 属性决定。
  • 例如:
    <canvas width="400" height="400"></canvas>
    
    上面的代码定义了一个 400x400 的逻辑像素大小。绘图时坐标范围为 (0, 0)(400, 400)

物理像素

  • 是 Canvas 在网页中实际显示的尺寸,由 CSS 样式的 widthheight 控制。
  • 例如:
    <canvas width="400" height="400" style="width: 200px; height: 200px;"></canvas>
    
    上面的代码将 Canvas 的显示缩小了一半,视觉上它是一个 200x200 的区域,但绘图坐标仍然是 (0, 0)(400, 400)

为什么鼠标事件会出现问题?

当鼠标点击 Canvas 时,offsetXoffsetY 是基于 物理像素 的鼠标位置,而绘图坐标是基于 逻辑像素 的。两者之间的比例由 Canvas 的逻辑大小和 CSS 显示大小的关系决定。

例如:

<canvas width="400" height="400" style="width: 200px; height: 200px;"></canvas>
  1. 逻辑像素:绘制一个矩形,坐标为 (50, 50, 100, 100)
  2. 物理像素:鼠标点击位置为 (100, 100),但因为显示缩小了一半,offsetX 实际代表逻辑坐标的 (200, 200)

结果:鼠标事件坐标和图形坐标完全对不上!


如何解决?

解决问题的关键是计算 逻辑坐标和物理坐标之间的缩放比例,然后对鼠标事件的坐标进行调整。

通用解决方案:动态计算缩放比例

通过 Canvas 的 getBoundingClientRect() 方法,可以获取 Canvas 在页面中的物理尺寸,再结合其逻辑大小计算缩放比例。

以下是完整代码实现:

handleMouseDown(e) {
  const dom = this.$refs.canvasRef; // 获取 Canvas DOM
  const rect = dom.getBoundingClientRect(); // 获取物理大小

  // 计算缩放比例
  const scaleX = dom.width / rect.width; // X 轴缩放比例
  const scaleY = dom.height / rect.height; // Y 轴缩放比例

  // 调整鼠标坐标
  const offsetX = e.offsetX * scaleX;
  const offsetY = e.offsetY * scaleY;

  console.log(`Adjusted Mouse Position: (${offsetX}, ${offsetY})`);
}

完整案例:支持高分辨率与 CSS 缩放的 Canvas 鼠标交互

以下是一个完整的 Canvas 鼠标点击交互案例,解决逻辑像素和物理像素不一致的问题。

HTML

<canvas id="myCanvas" width="800" height="800" style="width: 400px; height: 400px; border: 1px solid black;"></canvas>

JavaScript

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");

// 绘制一个矩形
ctx.fillStyle = "blue";
ctx.fillRect(200, 200, 200, 200);

// 监听鼠标点击事件
canvas.addEventListener("mousedown", (e) => {
  // 获取物理大小
  const rect = canvas.getBoundingClientRect();

  // 计算缩放比例
  const scaleX = canvas.width / rect.width;
  const scaleY = canvas.height / rect.height;

  // 调整鼠标坐标
  const offsetX = e.offsetX * scaleX;
  const offsetY = e.offsetY * scaleY;

  console.log(`Mouse Logical Position: (${offsetX}, ${offsetY})`);

  // 检测点击是否在矩形内
  if (
    offsetX >= 200 && offsetX <= 400 &&
    offsetY >= 200 && offsetY <= 400
  ) {
    alert("You clicked inside the rectangle!");
  }
});

适配高分辨率屏幕(Retina 屏幕)

在高分辨率屏幕上,Canvas 的默认显示分辨率可能不足,导致图形模糊。为了解决这个问题,可以在逻辑像素上增加分辨率,同时按比例调整 CSS 样式。

解决方案

  1. 设置高分辨率 Canvas:

    const dpr = window.devicePixelRatio || 1;
    canvas.width = 400 * dpr; // 提高逻辑像素
    canvas.height = 400 * dpr;
    canvas.style.width = "400px"; // 设置 CSS 尺寸
    canvas.style.height = "400px";
    
    ctx.scale(dpr, dpr); // 按设备像素比缩放绘图
    
  2. 鼠标事件仍然适用缩放比例,无需额外调整。


总结

核心点

  1. 逻辑像素 vs. 物理像素

    • 逻辑像素由 widthheight 定义,用于绘图。
    • 物理像素由 CSS 控制,影响显示大小。
  2. 鼠标事件坐标映射

    • 使用 getBoundingClientRect() 获取物理大小。
    • 根据缩放比例调整 offsetXoffsetY
  3. 适配高分辨率屏幕

    • 提升逻辑像素分辨率。
    • 使用 scale 方法确保绘图清晰。

代码复用性

  • 无论是适配高分辨率屏幕,还是处理鼠标事件,计算逻辑和物理像素缩放比例是通用的解决方案。这个方法不仅适用于 Canvas,也适用于其他需要精确像素计算的场景。

希望本文能帮助你更好地理解和解决 Canvas 中的坐标问题! 🎨

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/915370.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

nacos集群部署与配置

Nacos集群模式 1. 预备环境准备 请确保是在环境中安装使用: 64 bit OS Linux/Unix/Mac&#xff0c;推荐使用Linux系统。64 bit JDK 1.8&#xff1b;下载. 配置。Maven 3.2.x&#xff1b;下载. 配置。3个或3个以上Nacos节点才能构成集群 ubuntu中假如没安装jdk&#xff0c;则…

Python学习从0到1 day26 第三阶段 Spark ③ 数据计算 Ⅱ

目录 一、Filter方法 功能 语法 代码 总结 filter算子 二、distinct方法 功能 语法 代码 总结 distinct算子 三、SortBy方法 功能 语法 代码 总结 sortBy算子 四、数据计算练习 需求&#xff1a; 解答 总结 去重函数&#xff1a; 过滤函数&#xff1a; 转换函数&#xff1a; 排…

Jmeter基础篇(23)TPS和QPS的异同

前言 这是一篇性能测试指标的科普文章哦&#xff01; TPS和QPS是同一个概念吗&#xff1f; TPS&#xff08;Transactions Per Second&#xff09;和QPS&#xff08;Queries Per Second&#xff09;虽然都是衡量系统性能的指标&#xff0c;但是它们并不是同一个概念。这两个各…

IEC60870-5-104 协议源码架构详细分析

IEC60870-5-104 协议源码架构 前言一、资源三、目录层级一二、目录层级二config/lib60870_config.hdependencies/READMEexamplesCMakeLists.txtcs101_master_balancedcs104_client_asyncmulti_client_servertls_clienttls_server说明 make这些文件的作用是否需要导入这些文件&a…

机器学习在网络安全中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 机器学习在网络安全中的应用 机器学习在网络安全中的应用 机器学习在网络安全中的应用 引言 机器学习概述 定义与原理 发展历程 …

JUC基础类-AbstractQueuedSynchronizer

AbstractQueuedSynchronizer 1、AbstractQueuedSynchronizer概述2、AbstractQueuedSynchronizer源码分析2.1 AQS源码2.2 Node类 如有侵权&#xff0c;请联系&#xff5e; 如有问题&#xff0c;也欢迎批评指正&#xff5e; 1、AbstractQueuedSynchronizer概述 AbstractQueuedSy…

文献阅读 | Nature Methods:使用 STAMP 对空间转录组进行可解释的空间感知降维

文献介绍 文献题目&#xff1a; 使用 STAMP 对空间转录组进行可解释的空间感知降维 研究团队&#xff1a; 陈金妙&#xff08;新加坡科学技术研究局&#xff09; 发表时间&#xff1a; 2024-10-15 发表期刊&#xff1a; Nature Methods 影响因子&#xff1a; 36.1&#xff0…

Redis系列之底层数据结构ZipList

Redis系列之底层数据结构ZipList 实验环境 Redis 6.0 什么是Ziplist&#xff1f; Ziplist&#xff0c;压缩列表&#xff0c;这种数据结构会根据存入数据的类型和大小&#xff0c;分配大小不同的空间&#xff0c;所以是为了节省内存而采用的。因为这种数据结构是一种完整连续…

界面控件DevExpress WPF中文教程:TreeList视图及创建分配视图

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…

数据结构中数据有序性/ 单调性 ——二分查找

以下记录的都是闭区间写法 例题&#xff1a;34. 在排序数组中查找元素的第一个和最后一个位置 1.关系转换 寻找目标值有四种情况&#xff1a;≥、>、≤、< 比如目标值x&#xff0c; 可以转化为 ≥x、≥ x1、≤x、≤ x1 比如数组大小为6&#xff0c;目标值为…

探索Python的HTTP利器:Requests库的神秘面纱

文章目录 **探索Python的HTTP利器&#xff1a;Requests库的神秘面纱**一、背景&#xff1a;为何选择Requests库&#xff1f;二、Requests库是什么&#xff1f;三、如何安装Requests库&#xff1f;四、Requests库的五个简单函数使用方法1. GET请求2. POST请求3. PUT请求4. DELET…

《Linux从小白到高手》综合应用篇:深入详解Linux swap及其调整优化

1. 引言&#xff1a; Swap是存储设备上的一块空间&#xff08;分区&#xff09;&#xff0c;操作系统可以在这里暂存一些内存里放不下的东西。这从某种程度上相当于增加了服务器的可用内存。虽然从swap读写比内存慢&#xff0c;但总比没有好&#xff0c;算是内存不足时一种比较…

SpringMVC学习笔记(一)

一、SpringMVC的基本概念 &#xff08;一&#xff09;三层架构和MVC 1、三层架构概述 我们的开发架构一般都是基于两种形式&#xff0c;一种是 C/S 架构&#xff0c;也就是客户端/服务器&#xff0c;另一种是 B/S 架构&#xff0c;也就是浏览器服务器。在 JavaEE 开发中&…

小面馆叫号取餐流程 佳易王面馆米线店点餐叫号管理系统操作教程

一、概述 【软件资源文件下载在文章最后】 小面馆叫号取餐流程 佳易王面馆米线店点餐叫号管理系统操作教程 点餐软件以其实用的功能和简便的操作&#xff0c;为小型餐饮店提供了高效的点餐管理解决方案&#xff0c;提高了工作效率和服务质量 ‌点餐管理‌&#xff1a;支持电…

【.NET 8 实战--孢子记账--从单体到微服务】--简易权限--角色可访问接口管理

咱们继续来编写孢子记账的简易权限&#xff0c;这篇文章中我们将编写角色可访问接口的管理API&#xff0c;同样我不会把完整的代码全都列出来&#xff0c;只会列出部分代码&#xff0c;其余代码我希望大家能自己手动编写&#xff0c;然后对比项目代码。废话不多说&#xff0c;开…

Linux上Python使用MySQLdb包连接MySQL5.7和MySQL8的问题

在一台安装有MySQL8的Linux上用MySQLdb包连接MySQL5.7&#xff0c;连接参数中加上ssl_mode‘DISABLED’,能正常连接&#xff1b;不加ssl_mode参数&#xff0c;会报 而在连接MySQL8时加不加ssl_mode都能正常连接&#xff0c;但在使用过程&#xff0c;加了ssl_mode参数&#xff…

列表(list)

一、前言 本次博客主要讲解 list 容器的基本操作、常用接口做一个系统的整理&#xff0c;结合具体案例熟悉自定义内部排序方法的使用。如有任何错误&#xff0c;欢迎在评论区指出&#xff0c;我会积极改正。 二、什么是list list是C的一个序列容器&#xff0c;插入和删除元素…

spring使用xml文件整合事务+druid+mybatis

1.事务 事务&#xff08;Transaction&#xff09;是数据库管理系统中的一个重要概念&#xff0c;它表示一组不可分割的操作序列&#xff0c;这些操作要么全部执行成功&#xff0c;要么全部不执行&#xff0c;以确保数据库从一个一致性状态转换到另一个一致性状态。事务具有以下…

大语言模型LLM综述

一、LM主要发展阶段 1.1、统计语言模型SLM 基于统计学习方法&#xff0c;基本思想是基于马尔可夫假设HMM建立词概率预测模型。如n-gram语言模型 1.2、神经语言模型NLM 基于神经网络来做词的分布式表示。如word2vec模型 1.3、 预训练语言模型PLM 预训练一个网络模型来做词表…

【Jenkins实战】Windows安装服务启动失败

写此篇短文&#xff0c;望告诫后人。 如果你之前装过Jenkins&#xff0c;出于换域账号/本地帐号的原因想重新安装&#xff0c;你大概率会遇上一次Jenkins服务启动失败提示&#xff1a; Jenkins failed to start - Verify that you have sufficient privileges to start system…