绘制图像-clip方法

1.前言

绘制图像,之前我们介绍过drawImage方法

javascript 复制代码
ctx.drawImage(img, 300, 20, 500, 460, 20, 20, 1000, 920);
参数 含义 坐标系
300, 20 源图裁切起点 相对于图片左上角 (0,0)
500, 460 源图裁切大小 相对于图片
20, 20 画布绘制起点 相对于画布左上角 (0,0)
1000, 920 画布绘制大小 相对于画布

9 参数版是"主动去源图取东西" :从图片的 (300, 20) 位置开始,取 500x460 的区域,绘制到画布 (20, 20) 位置,缩放到 1000x920


2.Transform + Clip 版本详解

目标效果

transform + clip 实现 9 参数 drawImage 的等价效果:

javascript 复制代码
// 9 参数原版
ctx.drawImage(img, 300, 20, 500, 460, 20, 20, 1000, 920);

// transform + clip 版本
ctx.save();
ctx.translate(1100, 20);
ctx.scale(2.0, 2.0);
ctx.rect(0, 0, 500, 460);
ctx.clip();
ctx.drawImage(img, -300, -20);
ctx.restore();

Transform + Clip 版本的核心思路

不是"去源图取东西",而是"把图片拉到窗口下面"

  1. clip() 在画布上开了一个"窗口",只能看到窗口下面的内容
  2. 通过 translate/scale 变换坐标系
  3. 负坐标 把图片的 (300, 20) 区域"拉"到窗口下面

每一步的坐标系变化

初始状态

scss 复制代码
画布坐标系:原点在 (0, 0)

第 1 步:translate(1100, 20)

scss 复制代码
画布坐标系:原点移动到了 (1100, 20)
             现在的 (0, 0) 实际是画布的 (1100, 20)

第 2 步:scale(2.0, 2.0)

scss 复制代码
画布坐标系:单位长度放大 2 倍
             现在的 1 个单位 = 原来的 2 个像素
             现在的 (1, 1) 实际是画布的 (1100+2, 20+2) = (1102, 24)

第 3 步:rect(0, 0, 500, 460) + clip()

scss 复制代码
在**当前坐标系**中定义裁切区域:
- 起点:(0, 0) → 实际画布 (1100, 20)
- 大小:500x460 → 实际画布 1000x920(因为缩放了 2 倍)

所以 clip 区域实际是画布的 (1100, 20, 1000, 920)

第 4 步:drawImage(img, -300, -20)

markdown 复制代码
在**当前坐标系**中绘制图片:
- 图片的 (0, 0) 点画在 (-300, -20) 位置

换算成实际画布坐标:
- x: 1100 + (-300)*2 = 1100 - 600 = 500
- y: 20 + (-20)*2 = 20 - 40 = -20

关键理解:
- clip 区域在原点 (0, 0) 开始,大小 500x460(当前坐标系)
- 图片从 (-300, -20) 开始画
- 所以 clip 区域内显示的是图片的 (300, 20) 开始的部分

为什么要用负坐标?图解

clip 窗口(固定不动)

scss 复制代码
┌─────────────────────────┐
│                         │
│   (0,0)                 │  只能看到这个矩形内的东西
│   ┌─────────┐           │
│   │         │           │
│   │  可见   │           │
│   │  区域   │           │
│   │         │           │
│   └─────────┘           │
│                         │
└─────────────────────────┘

情况 1:drawImage(img, 0, 0) --- 图片左上角对齐窗口左上角

scss 复制代码
┌─────────────────────────┐
│ img(0,0) 从这里开始画    │
│ ┌───────────────────────┤
│ │■■■■■可见■■■■■         │  看到的是图片的 (0,0) 区域
│ └───────────────────────┤
─────────────────────────┘

情况 2:drawImage(img, -300, -20) --- 图片往左上角"拉"

scss 复制代码
┌─────────────────────────┐
│            img(0,0)      │  图片起点在窗口外面(左上)
│              ┌───────────┤
│              │■■■■■■■■■  │  窗口现在看到的是图片的
│              │■可见■     │  (300, 20) 区域!
│              │■■■■■■■■■  │
│              └───────────┤
└─────────────────────────┘

数学计算

scss 复制代码
clip 窗口:从 (0, 0) 开始,大小 500x460

drawImage(img, -300, -20) 的含义:
- 图片的 (0, 0) 点 画在 (-300, -20) 位置

那么 clip 窗口内的 (0, 0) 点,对应图片的哪个位置?
- 窗口 (0, 0) - 图片起点 (-300, -20) = 图片上的 (300, 20)

所以窗口内显示的就是图片的 (300, 20) 开始的区域!

对比总结

版本 思路 坐标
9 参数版 从源图取:从图片 (300, 20) 开始取 500x460 +300, +20(源图坐标)
transform 版 把图拉过来:把图片往反方向拉,让 (300, 20) 对齐窗口 -300, -20(绘制起点偏移)

关键理解

  • 9 参数版的 (300, 20)源图上的裁剪起点
  • transform 版的 (-300, -20)图片在画布上的绘制起点

两者方向相反,但效果等价!


为什么 clip 区域是 500x460,但显示是 1000x920?

javascript 复制代码
ctx.scale(2.0, 2.0);       // 先放大 2 倍
ctx.rect(0, 0, 500, 460);  // 定义 500x460 的裁切区域
ctx.clip();

clip() 是在 scale() 之后调用的,所以:

  • rect(0, 0, 500, 460) 是在缩放后的坐标系中定义的
  • 换算成实际画布像素:500*2 = 1000, 460*2 = 920
  • 所以 clip 区域实际是 1000x920

完整的变换链

scss 复制代码
原图 (1080x495)
    │
    │ drawImage(img, -300, -20)
    │ → 图片的 (300, 20) 点对齐到 clip 窗口的 (0, 0)
    ▼
clip 窗口 (500x460 @ 缩放坐标系)
    │
    │ scale(2.0, 2.0)
    │ → 放大到 1000x920 @ 画布像素
    ▼
画布显示区域 (1100, 20, 1000, 920)

记忆口诀

9 参数:从源图"取"东西 → 正坐标 (300, 20)
transform:把图片"拉"过来 → 负坐标 (-300, -20)

源代码案例

xml 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body{
            margin: 0;
            padding: 0;
            overflow: hidden;
        }
    </style>
</head>
<body>
    <canvas id="myCanvas"></canvas>
    <script>
        var canvas = document.getElementById("myCanvas");
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
        var ctx = canvas.getContext("2d");
        var img = new Image();
        img.src = "./imgs/月之暗面.png";
        img.onload = function(){
            console.log('图片尺寸:', img.width, 'x', img.height);

            // 一.9参数原版
            ctx.drawImage(img, 300, 20, 500, 460, 20, 20, 1000, 920);

            // 用绿框标出
            ctx.strokeStyle = 'lime';
            ctx.lineWidth = 5;
            ctx.strokeRect(20, 20, 1000, 920);

            // ---------------------
            // 二.transform + clip 版本
            ctx.save();

            // 1. 位移到目标位置
            ctx.translate(1100, 20);

            // 2. 缩放 2 倍
            ctx.scale(2.0, 2.0);

            // 3. 裁切 500x460 区域
            ctx.beginPath();
            ctx.rect(0, 0, 500, 460);
            ctx.clip();

            // 4. 绘图:关键!用负坐标让源图的 (300, 20) 对齐到 clip 区域的 (0, 0)
            ctx.drawImage(img, -300, -20);//就是drawImage3个参数版 👉 源图只做"定位" (位移)

            ctx.restore();

            // 红框标出
            ctx.beginPath();
            ctx.strokeStyle = 'red';
            ctx.lineWidth = 5;
            ctx.strokeRect(1100, 20, 1000, 920);
        }
    </script>
</body>
</html>
相关推荐
tedcloud1234 小时前
UI-TARS-desktop部署教程:构建AI桌面自动化系统
服务器·前端·人工智能·ui·自动化·github
UXbot7 小时前
AI原型设计工具如何支持团队协作与快速迭代
前端·交互·个人开发·ai编程·原型模式
ZC跨境爬虫7 小时前
跟着MDN学HTML_day_48:(Node接口)
前端·javascript·ui·html·音视频
PieroPc9 小时前
CAMWATCH — 局域网摄像头监控系统 Fastapi + html
前端·python·html·fastapi·监控
巴巴博一10 小时前
2026 最新:Trae / Cursor 一键接入 taste-skill 完整教程(让 AI 前端告别“AI 味”)
前端·ai·ai编程
kyriewen10 小时前
半夜三点线上崩了,AI替我背了锅——用AI排错,五分钟定位三年老bug
前端·javascript·ai编程
kyriewen10 小时前
我让 AI 当了 24 小时全年无休的“毒舌考官”
前端·ci/cd·ai编程
hexu_blog11 小时前
vue+java实现图片批量压缩
java·前端·vue.js
IT_陈寒11 小时前
为什么你应该学习JavaScript?
前端·人工智能·后端
lifejump11 小时前
Empire(帝国)CMS 7.5 XSS注入
前端·安全·xss