当前位置: 首页 > article >正文

逻辑像素与物理像素——canvas缩放后绘图区域的长宽究竟是多少

bug描述

最近在基于 canvas写一个页面,涉及在画布中绘制网格。为了适配高分辨率的屏幕,给画布做了缩放,用缩放后的canvas长宽去计算网格的行列数。
以下是代码

 	// 获取设备像素比
     const devicePixelRatio = window.devicePixelRatio || 1;
     // 获取CSS宽高
     const styleWidth = backgroundCanvas.clientWidth;
     const styleHeight = backgroundCanvas.clientHeight;

     // 设置画布的实际绘图分辨率
     backgroundCanvas.width = styleWidth * devicePixelRatio;
     backgroundCanvas.height = styleHeight * devicePixelRatio;
     // 缩放绘图上下文,适配设备像素比
     backCtx.scale(devicePixelRatio, devicePixelRatio);
  	//计算网格行列
     cols = Math.floor(styleWidth / gridSize);
     rows = Math.floor(styleHeight / gridSize);;

结果很奇怪啊,计算得 cols = 27,但屏幕只显示了 21 列。


问题分析

  • Canvas 的逻辑宽高 vs. 物理分辨率

    • canvas.widthcanvas.height 定义了画布的实际绘图分辨率,单位是 物理像素
    • CSS 设置的 widthheight 是画布的 逻辑显示尺寸,单位是 逻辑像素
    • 当设置 canvas.width = styleWidth * devicePixelRatio 时,画布的绘图区域被放大,但 CSS 控制的逻辑显示尺寸未改变。
  • 导致问题的核心原因

    1. 网格行列数基于物理分辨率计算:
      const cols = Math.floor(backgroundCanvas.width / gridSize);
      
      由于 backgroundCanvas.width 是物理像素,计算的网格行列数和逻辑尺寸中实际能够显示的会不同。

解决方案

解决该问题的关键在于 在逻辑尺寸范围内计算网格

网格绘制需要基于逻辑宽高进行坐标计算:

 // 获取CSS宽高
const styleWidth = backgroundCanvas.clientWidth;
const styleHeight = backgroundCanvas.clientHeight;
 ....
cols = Math.floor(styleWidth / gridSize);
rows = Math.floor(styleHeight / gridSize);

完整代码示例

以下是修复后整理的完整代码:

const devicePixelRatio = window.devicePixelRatio || 1;
const gridSize = 20;

const backgroundCanvas = document.getElementById('background');
const styleWidth = backgroundCanvas.clientWidth; // CSS 逻辑宽度
const styleHeight = backgroundCanvas.clientHeight; // CSS 逻辑高度

// 设置物理分辨率
backgroundCanvas.width = styleWidth * devicePixelRatio;
backgroundCanvas.height = styleHeight * devicePixelRatio;

// 确保逻辑显示尺寸不变
backgroundCanvas.style.width = `${styleWidth}px`;
backgroundCanvas.style.height = `${styleHeight}px`;

// 获取上下文并缩放
const ctx = backgroundCanvas.getContext('2d');
ctx.scale(devicePixelRatio, devicePixelRatio);

// 计算逻辑行列数
const cols = Math.floor(styleWidth / gridSize);
const rows = Math.floor(styleHeight / gridSize);

// 绘制网格
function drawGrid() {
    ctx.clearRect(0, 0, backgroundCanvas.width, backgroundCanvas.height);
    ctx.strokeStyle = "#ccc";

    for (let x = 0; x <= cols; x++) {
        ctx.beginPath();
        ctx.moveTo(x * gridSize, 0);
        ctx.lineTo(x * gridSize, styleHeight);
        ctx.stroke();
    }

    for (let y = 0; y <= rows; y++) {
        ctx.beginPath();
        ctx.moveTo(0, y * gridSize);
        ctx.lineTo(styleWidth, y * gridSize);
        ctx.stroke();
    }
}

drawGrid();

总结

  • Bug 的本质:物理分辨率(canvas.widthcanvas.height)与逻辑显示尺寸(CSS 控制的宽高)不匹配,导致网格计算和绘制错位。
  • 解决方案:缩放后,使用canvas的尺寸计算应该基于逻辑尺寸

http://www.kler.cn/a/408694.html

相关文章:

  • 【计算机网络】多路转接之poll
  • mybatis学习(三)
  • 【C】错误的变量定义导致sprintf()‌输出错误
  • 使用 helm 部署 gitlab
  • Android开发实战班 - 数据持久化 - 数据加密与安全
  • 解决 Android 单元测试 No tests found for given includes:
  • draggable的el-dialog实现对话框标题可以选择
  • 一篇保姆式centos/ubuntu安装docker
  • 内网渗透横向移动1
  • 鸿蒙开发——根据背景图片来构建特定颜色的蒙版
  • mac安装appuim
  • PD虚拟机启动后 Mac主机无法上网解决教程
  • 树莓派——Opencv_python基本操作
  • 【接口封装】——5、绘制头像
  • c++的类和对象(1)
  • PDF内容提取,MinerU使用
  • vscode + ROS 配置快捷编译
  • 【C++】从C语言到C++学习指南
  • GESP语法知识(快速排序)
  • VRRP虚拟路由实现主备设备负载分担
  • 在Spring Boot项目中集成RabbitMQ消息中间件
  • JSON 性能测试 - WastJson 性能也很快
  • 基于LiteFlow的风控系统指标版本控制
  • ApiChain 从迭代测试用例到项目回归测试 核心使用教程
  • 知乎日报——第二周
  • 乐鑫ESP32物联网方案,设备人机交互技术应用,启明云端乐鑫代理商