canvas画出抖音点赞动画效果demo
粘贴代码可以直接测试(图片自行更换)
html代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>点赞demo</title>
<script src="https://unpkg.com/jquery@3.3.1/dist/jquery.min.js"></script>
<script src="../js/praisecanvas.js"></script>
</head>
<style type="text/css">
.canvas-box{
pointer-events:none;
z-index:100;
position:absolute;
right:20%;
bottom:50px
}
.praise-btn{
position:absolute;
right:28%;
display:inline-block;
cursor: pointer;
width:80px;
height:35px;
}
.bdiv {
position: absolute;
bottom: 20px;
height: 20px;
width: 100%;
border: 1px solid red;
}
</style>
<body>
<div class="bdiv">
<button type="button" id="praisebtn" class="praise-btn" style="" onclick="onPraiseBtnClick()">点赞demo</button>
</div>
<div>
<canvas class="canvas-box" id="praisecanvas" width="160" height="400px" style="" ></canvas>
</div>
<script>
var thumbsUpAni = null;
var needLoopCount = null;
function onPraiseBtnClick() {
if (thumbsUpAni == null)
thumbsUpAni = new ThumbsUpAni("praisecanvas");
needLoopCount += Math.ceil(Math.random() * 5) + 1;
bullet_renderloop()
}
function bullet_renderloop() {
if (thumbsUpAni != null && needLoopCount > 0) {
if (Math.random() * 10 > 8) {
thumbsUpAni.start();
needLoopCount--;
if (needLoopCount <= 0)
needLoopCount = 0;
}
}
requestAnimationFrame(bullet_renderloop);
}
</script>
</body>
</html>
praisecanvas.js 代码
/**
* >=min && <=max
* @param min
* @param max
*/
function getRandom(min, max) {
return min + Math.floor(Math.random() * (max - min + 1))
}
function ThumbsUpAni(canvasId) {
this.imgsList = [];
this.context = null;
this.width = 0;
this.height = 0;
this.scanning = false;
this.renderList=[];
this.scaleTime = 0.1;// 百分比
this.loadImages();
const canvas = document.getElementById(canvasId);
this.context = canvas.getContext('2d');
this.width = canvas.width;
this.height = canvas.height;
}
ThumbsUpAni.prototype.loadImages = function() {
const images = [
'/static/images/praise/bg1.png',
'/static/images/praise/bg2.png',
'/static/images/praise/bg3.png',
'/static/images/praise/bg4.png',
'/static/images/praise/bg5.png',
'/static/images/praise/bg6.png',
'/static/images/praise/gift1_small.png',
'/static/images/praise/start1_small.png',
'/static/images/praise/start2_small.png',
'/static/images/praise/start3_small.png',
];
const promiseAll = [];
images.forEach((src) => {
const p = new Promise(function (resolve) {
const img = new Image;
img.onerror = img.onload = resolve.bind(null, img);
img.src = src;
});
promiseAll.push(p);
});
Promise.all(promiseAll).then((imgsList) => {
this.imgsList = imgsList.filter((d) => {
if (d && d.width > 0) return true;
return false;
});
if (this.imgsList.length == 0) {
dLog('error', 'imgsList load all error');
return;
}
})
}
ThumbsUpAni.prototype.createRender = function() {
if (this.imgsList.length == 0) return null;
const basicScale = [0.4, 0.8, 1.0][getRandom(0, 1)];
const getScale = (diffTime) => {
if (diffTime < this.scaleTime) {
return +((diffTime/ this.scaleTime).toFixed(2)) * basicScale;
} else {
return basicScale;
}
};
const context = this.context;
// 随机读取一个图片来渲染
const image = this.imgsList[getRandom(0, this.imgsList.length - 1)]
const offset = 20;
const basicX = this.width / 2 + getRandom(-offset, offset);
const angle = getRandom(2, 10);
let ratio = getRandom(10,30)*((getRandom(0, 1) ? 1 : -1));
const getTranslateX = (diffTime) => {
if (diffTime < this.scaleTime) {// 放大期间,不进行摇摆位移
return basicX;
} else {
return basicX + ratio*Math.sin(angle*(diffTime - this.scaleTime));
}
};
const getTranslateY = (diffTime) => {
return image.height / 2 + (this.height - image.height / 2) * (1-diffTime);
};
const fadeOutStage = getRandom(14, 18) / 100;
const getAlpha = (diffTime) => {
let left = 1 - +diffTime;
if (left > fadeOutStage) {
return 1;
} else {
return 1 - +((fadeOutStage - left) / fadeOutStage).toFixed(2);
}
};
return (diffTime) => {
// 差值满了,即结束了 0 ---》 1
if(diffTime>=1) return true;
context.save();
const scale = getScale(diffTime);
// const rotate = getRotate();
const translateX = getTranslateX(diffTime);
const translateY = getTranslateY(diffTime);
context.translate(translateX, translateY);
context.scale(scale, scale);
// context.rotate(rotate * Math.PI / 180);
context.globalAlpha = getAlpha(diffTime);
context.drawImage(
image,
-image.width / 2,
-image.height / 2,
image.width,
image.height
);
context.restore();
};
}
ThumbsUpAni.prototype.scan = function() {
this.context.clearRect(0, 0, this.width, this.height);
this.context.fillStyle = "rgba(255,255,255,0)";
this.context.fillRect(0,0,200,400);
let index = 0;
let length = this.renderList.length;
if (length > 0) {
requestFrame(this.scan.bind(this));
this.scanning = true;
} else {
this.scanning = false;
}
while (index < length) {
const child = this.renderList[index];
if (!child || !child.render || child.render.call(null, (Date.now() - child.timestamp) / child.duration)) {
// 结束了,删除该动画
this.renderList.splice(index, 1);
length--;
} else {
// continue
index++;
}
}
}
ThumbsUpAni.prototype.start = function() {
const render = this.createRender();
const duration = getRandom(1500, 3000);
this.renderList.push({
render,
duration,
timestamp: Date.now(),
});
if (!this.scanning) {
this.scanning = true;
requestFrame(this.scan.bind(this));
}
return this;
}
function requestFrame(cb) {
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
}
)(cb);
}