Loading

canvas缩放动画

最近看到一个h5不停缩放的效果,感觉还挺有趣的,就研究了一下,这里做一下记录。先来看看效果:
image

动画主要是靠canvas实现的,每一次缩放就是在画布上进行一次绘画。绘画时需要绘两张图片,第一张是从大到小,第二张是从小到大。从大到小是整体缩放,从小到大是局部放大。由于是在一个区域里的变化,就呈现出了两张图片都在缩小的感觉。
完成这一效果主要依靠的工具就是drawImage()。这个函数有9个参数,分别是img,sx,sy,swidth,sheight,x,y,width,height。我最初看的时候以为自己看懂了,结果在用的时候出现了很多问题,找了很多资料发现确实是自己理解能力有问题。这里贴一张图记录下:
image
还有一个问题就是放大和缩小是在同时进行的,每一次的变化都对应一个比例,因此这里需要对缩小的最终效果进行计算,得出每次缩放的比例和缩放完成时退出条件。
代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <style>
    .canvas-wrap, .img-wrap{
      margin: auto;
      width: 270px;
      height: 480px;
      background-color:blueviolet;
    }
    #canvas {
      width: 100%;
      height: 100%;
    }
    #btnPlay {
      margin: 10px auto;
      width: 84px;
      height: 45px;
      line-height: 45px;
      background-color:blueviolet;
      color: #fff;
      text-align: center;
    }
    .img-wrap {
      opacity: 0;
    }
  </style>
  <body>
    <div class="canvas-wrap" @click="showResult">
      <canvas class="canvas" id="canvas" width="540" height="960"></canvas>
    </div>
    <div class="btn-play" id="btnPlay" @touchstart="playMusic">长按</div>
    <div class="img-wrap"></div>
    <script src="./scaleCanvas.js"></script>
    <script>
      const canvas = document.getElementById('canvas');
      const ctx = canvas.getContext('2d');
      let imgList = [
        {
          link: './images/img1.jpg',
        },
        {
          link: './images/img2.jpg',
        },
        {
          link: './images/img3.jpg',
        },
      ];
      new scaleCanvas(ctx,imgList).init();
    </script>
  </body>
</html>

scaleCanvas类:

const canvasWidth = 540;
const canvasHeight = 960;
const imgW = 1080;
const imgH = 1920;

let countDown;
let countNext;
class scaleCanvas {
  constructor(ctx, imgList = []) {
    this.ctx = ctx;
    this.imgList = imgList;
    this.radio = 1;
    this.index = 0;
    this.scale = 0.99;
  }
  loadImg() {
    const imgPromises = this.imgList.map(
      (item, index) =>
        new Promise((resolve, reject) => {
          const img = new Image();
          img.src = item.link;
          img.name = index;
          img.className = 'item';
          img.onload = () => {
            document.getElementsByClassName('img-wrap')[0].append(img);
            resolve();
          };
          img.onerror = () => reject();
        })
    );
    return Promise.all(imgPromises);
  }
  async init() {
    await this.loadImg();
    let tempitem = document.getElementsByClassName('item');
    let tempDom = [];
    Array.prototype.forEach.call(tempitem, function (i) {
      tempDom.push(i);
    });

    //排序
    this.domImg = tempDom.sort(function (pre, cur) {
      return pre.name - cur.name;
    });

    //开始绘画
    this.containerImage = this.domImg[this.index + 1];
    this.innerImage = this.domImg[this.index];

    this.draw();
    //绑定函数
    const startTouch = this.touchHandler.bind(this);
    const endTouch = this.touchendHandler.bind(this);
    let btnpaly = document.getElementById('btnPlay');
    btnpaly.addEventListener('mousedown', function () {
      countDown = setTimeout(() => {
        console.log('按下鼠标');
        startTouch();
      }, 1000);
    });
    btnpaly.addEventListener('mouseup', function () {
      console.log('mouseup');
      clearTimeout(countDown);
      clearTimeout(countNext);
      endTouch();
    });
  }
  touchHandler(e) {
    e && e.stopPropagation();
    const changeRadio = () => {
      this.radio = this.radio * this.scale;
      this.timeCircle = requestAnimationFrame(changeRadio);
      this.draw();
    };
    requestAnimationFrame(changeRadio);
  }
  touchendHandler(e) {
    e && e.stopPropagation();
    console.log('停止渲染');
    cancelAnimationFrame(this.timeCircle);
  }
  draw() {
    if (this.index + 1 == this.imgList.length) {
      this.touchendHandler();
      alert('success');
      return;
    }
    
    if (this.radio < 0.14) {
      console.log('缩放完一页');
      // 缩放完一页
      this.touchendHandler();

      countNext = setTimeout(() => {
        this.index++;
        this.radio = 1;
        this.touchHandler();
        console.log(this.index);
      }, 2000);
      console.log(this.index);
    }

    this.imgNext = this.imgList[this.index + 1];
    this.imgCur = this.imgList[this.index];
    this.outsideImage = this.domImg[this.index + 1];
    this.innerImage = this.domImg[this.index];
    this.drawImgOversize(this.outsideImage, this.radio);
    this.drawImgMinisize(this.innerImage, this.radio);
    
  }
  drawImgOversize(i, r) {
    //console.log(i);
    this.ctx.drawImage(i, 0, imgH * (r - 0.12), imgW * (1.13 - r), imgH * (1.1 - r), 0, 0, canvasWidth, canvasHeight);
  }
  drawImgMinisize(i, r) {
    this.ctx.drawImage(i, 0, 0, imgW, imgH, 31 * (1 - r), 710 * (1 - r), canvasWidth * r, canvasHeight * r);
  }
}
// canvas.drawImage()
// img	规定要使用的图像、画布或视频。
// sx	可选。开始剪切的 x 坐标位置。
// sy	可选。开始剪切的 y 坐标位置。
// swidth	可选。被剪切图像的宽度。
// sheight	可选。被剪切图像的高度。
// x	在画布上放置图像的 x 坐标位置。
// y	在画布上放置图像的 y 坐标位置。
// width	可选。要使用的图像的宽度(伸展或缩小图像)。
// height	可选。要使用的图像的高度(伸展或缩小图像)。

最后在补充一下canvas画布模糊的情况。在css中设置的画布大小是指dom最终显示大小。而由于devicePixelRatio(设备像素比)的存在,导致只有1px的大小要现在在2px的大小上,形成了模糊。所以要在canvas标签上设置width和height,大小应为在css中设置的2倍。使用drawImage绘画时位置和大小按照标签上的大小来计算。

posted @ 2023-03-11 00:15  淡蓝色的点  阅读(226)  评论(0)    收藏  举报