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

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

还有一个问题就是放大和缩小是在同时进行的,每一次的变化都对应一个比例,因此这里需要对缩小的最终效果进行计算,得出每次缩放的比例和缩放完成时退出条件。
代码:
<!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绘画时位置和大小按照标签上的大小来计算。

浙公网安备 33010602011771号