JavaScript Canvas 实现图片碎片切割与菱形扩散动画

作者:窗边听雨时间:2026-10-25 22:18:06浏览:540

本文详细介绍如何利用 HTML5 Canvas 实现炫酷的图片碎片切割与菱形扩散动画效果。通过解析 drawImage 方法的九个参数,结合坐标系切割逻辑与菱形扩散算法,逐步演示如何将图片分割为小方块并动态渲染。文章包含完整的代码实现与参数说明,帮助开发者掌握 Canvas 图像处理与动画控制技巧。

前言

本文分享一种基于 Canvas 实现的图片碎片切割与扩散动画效果。该效果在之前的博客中曾有过类似实现,因其视觉表现力较强,故再次整理。虽然代码量不大,但涉及部分几何计算与 Canvas 渲染逻辑,需要一定的数学基础来理解。遵循惯例,我们先展示最终效果,再深入解析实现步骤。

前言

需求分析

观察效果可知,图片切换时,画面会分解为多个小碎片,从点击位置开始呈菱形向外扩散。在页面中,这些碎片表现为一个个独立的小方块。实现的核心难点在于两点:一是如何将完整图片精确切割并分别渲染这些小块;二是如何确定菱形扩散图案中各点的具体位置。

  • 切割:这里我们可以以坐标系的形式来进行切割,每一个方块都对应着它们自己在坐标系中的位置(x, y)
  • 绘制:这里的重点在于drawImage方法
  • 棱形扩散:这里需要点数学几何理解能力,后面作图理解

实现过程

坐标系

在编写代码前,需明确一个概念:「坐标系」。

注意:这里所说的坐标系并非数学标准坐标系,二者存在差异。主要区别在于原点位置以及 Y 轴的方向设定不同。

前言

切割

此步骤旨在确定每个单元格的大小。单元格的长宽不宜过大或过小:过大则视觉效果不够精细,过小则会增加性能负担。

本例中画板尺寸为 800 * 530,选取单元格尺寸为 16 * 15。由此计算,整个画布由 50 * 35 共 1750 个单元格组成。完成单元格划分后,需先计算一些基础参数供后续使用。

this.imgW = 800; // 图片原始宽
this.imgH = 530; // 图片原始高

this.conW = 800; // 画布宽
this.conH = 530; //  画布高

this.dw = 16; // 单元格宽
this.dh = 15; // 单元格高

this.I = this.conH / this.dh; //单元行数
this.J = this.conW / this.dw; // 单元列数

this.DW = this.imgW / this.J; // 原图单元宽
this.DH = this.imgH / this.I; // 原图单元高

「行数 = 画布高度 / 单元格高度;列数 = 画面宽度 / 单元格宽度」

绘制

本次绘制的核心在于 drawImage 方法,我们先来了解该方法的参数及其功能。

drawImage

drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

该方法包含 9 个参数,用于在画布上绘制图像。面对如此多的参数,初学者可能会感到困惑,但掌握规律后便不难理解。

  • 「image」:绘制到上下文的元素。允许任何的画布图像源,例如:HTMLImageElement、SVGImageElement、HTMLVideoElement、HTMLCanvasElement、ImageBitmap、OffscreenCanvas或 VideoFrame 。
  • 「sx」:(可选)需要绘制到目标上下文中的,image 的矩形(裁剪)选择框的左上角 X 轴坐标。可以使用 3 参数或 5 参数语法来省略这个参数。
  • 「s y」:(可选)需要绘制到目标上下文中的,image 的矩形(裁剪)选择框的左上角 Y 轴坐标。可以使用 3 参数或 5 参数语法来省略这个参数。
  • 「sWidth」:(可选)需要绘制到目标上下文中的,image 的矩形(裁剪)选择框的宽度。如果不说明,整个矩形(裁剪)从坐标的 sx 和 sy 开始,到 image 的右下角结束。可以使用 3 参数或 5 参数语法来省略这个参数。使用负值将翻转这个图像。
  • 「sHeight」:(可选)需要绘制到目标上下文中的,image的矩形(裁剪)选择框的高度。使用负值将翻转这个图像。
  • 「dx」:image 的左上角在目标画布上 X 轴坐标。
  • 「dy」:image 的左上角在目标画布上 Y 轴坐标。
  • 「dWidth」:image 在目标画布上绘制的宽度。允许对绘制的 image 进行缩放。如果不说明,在绘制时 image 宽度不会缩放。注意,这个参数不包含在 3 参数语法中。
  • 「dHeight」:image 在目标画布上绘制的高度。允许对绘制的 image 进行缩放。如果不说明,在绘制时 image 高度不会缩放。注意,这个参数不包含在 3 参数语法中。

这9个参数我们可以这样来记忆,第一个参数是图像源,接下来的四个参数指的是原图,最后四个参数指的是画布

切割&渲染

主要逻辑是将一张图片切割成多个小碎片,这些碎片拼接后应还原为完整图片。

class ChipBanner {
  constructor() {
    this.cvs = document.querySelector("#chip");
    this.ctx = this.cvs.getContext("2d");
    this.imgList = document.querySelectorAll(".bg");
    this.imgIndex = 0;
    this.isAnimating = false;

    this.imgW = 800; //图片原始宽/高
    this.imgH = 530;

    this.conW = 800; //画布宽/高
    this.conH = 530;

    this.dw = 16; //画布单元宽/高
    this.dh = 15;

    this.I = this.conH / this.dh; //单元行/列数
    this.J = this.conW / this.dw;

    this.DW = this.imgW / this.J; //原图单元宽/高
    this.DH = this.imgH / this.I;
  }

  init() {
    this.ctx.beginPath();

    for (let i = 0; i < this.I; i++) {
      for (let j = 0; j < this.J; j++) {
        this.chipDraw(this.imgList[this.imgIndex], i, j);
      }
    }

    this.ctx.closePath();
    this.ctx.stroke();
  }
  drawText() {
    this.ctx.font = "150px serif";
    this.ctx.strokeStyle = "white";
    this.ctx.strokeText("1024", 500, 500);
  }

  chipDraw(img, i, j) {
    this.drawText();
    //负责绘制,i: 单元行号;j: 单元列号
    this.ctx.drawImage(
      img,
      this.DW * j,
      this.DH * i,
      this.DW,
      this.DH,
      this.dw * j,
      this.dh * i,
      this.dw,
      this.dh
    );
  }
}

若坐标计算正确,拼接后的视觉效果与正常图片无异。

前言

再来看一张拼错的图

前言

若初始几何坐标计算有误,拼接结果将如图所示,看起来如同动画帧卡住一般。

动画

动画部分的关键在于找出点击点周围菱形范围内的所有点坐标。在清除这些坐标对应的图案后,开始绘制下一张图片,从而形成扩散动画。

「菱形线上的点与坐标的 行号差值的绝对值 + 列号差值的绝对值 = 距离」

找出坐标棱形范围内所有的点

countAround(i, j, dst) {
    let arr = [];
    for (let m = i - dst; m <= i + dst; m++) {
      for (let n = j - dst; n <= j + dst; n++) {
        if (
          Math.abs(m - i) + Math.abs(n - j) == dst &&
          m >= 0 &&
          n >= 0 &&
          m <= this.I - 1 &&
          n <= this.J - 1
        ) {
          arr.push({ x: m, y: n });
        }
      }
    }
    return arr;
  }

清除单元格画布

chipClear(i, j) {
    this.ctx.clearRect(this.dw * j, this.dh * i, this.dw, this.dh);
}

合并&动画

start(i, j) {
    if (this.isAnimating) return;

    this.isAnimating = true;

    this.imgIndex++;

    if (this.imgIndex > this.imgList.length - 1) this.imgIndex = 0;

    let _this = this,
      dst = 0,
      timer = setInterval(() => {
        let resArr = _this.countAround(i, j, dst);

        resArr.forEach((item) => {
          _this.chipClear(item.x, item.y);  // 清除单元格
          _this.chipDraw(_this.imgList[_this.imgIndex], item.x, item.y); // 绘制下一张图片
        });

        if (!resArr.length) {
          clearInterval(timer);
          _this.isAnimating = false;
        }
        dst++;
      }, 30);
  }

大功告成,这样就实现了一个炫酷的碎片式切图效果了~

前言

到此这篇关于JavaScript利用canvas实现炫酷的碎片切图效果的文章就介绍到这了,更多相关JavaScript canvas碎片切图效果内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

作者最新文章