class HeartsFlow { constructor(data) { this.el = document.querySelector(data.canvasEl); this.w = 200; this.h = 400; this.ctx = this.el.getContext('2d'); this.colors = [ '255, 137, 164', //'#FF89A4', '239, 121, 138', //'#EF798A', '255, 77, 128', //'#FF4D80', '249, 42, 130' //'#F92A82' ]; this.heartsAmount = data.amount; this.heartsList = []; this.isAnimate = false; this.raf = null; this.animate = this.animate.bind(this); this.paintHeart = this.paintHeart.bind(this); this.stopAnimation = this.stopAnimation.bind(this); this.init(); } getRandomColor() { return this.colors[Math.floor(Math.random() * this.colors.length)]; } getRandom(min, max) { return Math.floor(Math.random() * (max - min) + min); } setHeartsList() { let arr = []; for (let i = 0; i < this.heartsAmount; i++) { let currentSize = this.getRandom(10, 15); let dt = { x: this.w / 2, y: this.h, bx: this.w / 2, by: this.h, pos: this.h, _osp: this.getRandom(200, 400) / 100, osp: this.getRandom(11, 12) / 10, vsp: this.getRandom(currentSize, currentSize + i * 2) / 1000, size: currentSize, color: this.getRandomColor(), alfa: 1 }; arr.push(dt); } this.heartsList = [...this.heartsList, ...arr]; } getCoordinates({ x, y, size, color, bx, by, _osp, osp, vsp, pos, alfa }) { return { xst: x, yst: y + size / 2, x0: x - size / 1.4, y0: y + size / 4, x1: x - size / 1.3, y1: y - size / 1.3, _x0: x + size / 1.4, _y0: y + size / 4, _x1: x + size / 1.3, _y1: y - size / 1.3, xfn: x, yfn: y - size / 3, bx: bx, by: by, _osp: _osp, osp: osp, vsp: vsp, pos: pos, alfa: alfa, size: size, color: color }; } paintHeart({ xst, yst, x0, y0, _x0, _y0, x1, y1, _x1, _y1, xfn, yfn, color, alfa }) { this.ctx.globalCompositeOperation = "lighter"; this.ctx.beginPath(); this.ctx.moveTo(xst, yst); this.ctx.bezierCurveTo(x0, y0, x1, y1, xfn, yfn); this.ctx.moveTo(xst, yst); this.ctx.bezierCurveTo(_x0, _y0, _x1, _y1, xfn, yfn); this.ctx.fillStyle = `rgba(${color}, ${alfa})`; this.ctx.strokeStyle = `rgba(${color}, ${alfa})`; this.ctx.fill(); this.ctx.stroke(); this.ctx.closePath(); } mutateData() { this.heartsList = this.heartsList.map(item => { let pos = item.pos - 0.05; let x = item.x + Math.sin(pos * item._osp) * ((pos - item.by) / item.osp); let y = pos + (pos - item.by) / item.vsp * 1.6; let alfa = this.normalize0between1(0, this.h, y).toFixed(1); return { ...item, x: x, y: y, pos: pos, alfa: alfa }; }); this.heartsList = this.heartsList.filter(item => item.y > 0); } normalize0between1(min, max, value) { return (value - min) / (max - min); } setCanvas() { this.el.width = this.w; this.el.height = this.h; } startAnimation() { if (!this.isAnimate) { this.isAnimate = true; console.log('start animation'); this.setHeartsList(); this.animate(); } else { this.setHeartsList(); } } stopAnimation() { this.isAnimate = false; console.log('stop animation'); cancelAnimationFrame(this.raf); } animate() { this.ctx.clearRect(0, 0, this.w, this.h); if (this.isAnimate) { for (let i = 0, len = this.heartsList.length; i < len; i++) { let hrt = this.getCoordinates(this.heartsList[i]); this.paintHeart(hrt); } this.mutateData(); } this.raf = requestAnimationFrame(this.animate); if (this.heartsList.length === 0 && this.isAnimate) { this.stopAnimation(); } } init() { this.setCanvas(); this.setHeartsList(); this.animate(); }} // let ht = new HeartsFlow({ // canvasEl: '.hearts-canvas', // amount: 20 }); // let btn = document.querySelector('.btn'); // btn.addEventListener('click', function () { // ht.startAnimation(); // });