---
# System prepended metadata

title: Bumping Heart ❤️

---

# Bumping Heart ❤️

```html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
 <HEAD>
    <TITLE> Heart </TITLE>
    <META NAME="Generator" CONTENT="EditPlus">
    <META NAME="Author" CONTENT="">
    <META NAME="Keywords" CONTENT="">
    <META NAME="Description" CONTENT="">
    <style>

        @keyframes bumping {
            0% {
                width: 50%;
                height: 50%;
            }
            40% {
                width: 50%;
                height: 50%;
            }
            50% {
                width: 55%;
                height: 55%;
            }
            57% {
                width: 45%;
                height: 45%;
            }
            65% {
                width: 50%;
                height: 50%;
            }
            100% {
                width: 50%;
                height: 50%;
            }
        }

        html, body {
            height: 100%;
            padding: 0;
            margin: 0;
            background: black;
        }
        body {
            display: flex;
            align-items: center;
            align-content: center;
            justify-content: center;
        }
        canvas#pinkboard {
            position: absolute;
            width: 50%;
            height: 50%;
            animation-name: bumping;
            animation-duration: 1.5s;
            animation-delay: 5s;
            animation-iteration-count: infinite;
            z-index: 1000;
            transform: scale(0.85);
        }
        canvas#innerLayer {
            position: absolute;
            width: 50%;
            height: 50%;
            animation-name: bumping;
            animation-duration: 1.5s;
            animation-delay: 5s;
            animation-iteration-count: infinite;
            transform: scale(1.1);
        }
    </style>
 </HEAD>

 <BODY>
    <canvas id="pinkboard"></canvas>
    <canvas id="innerLayer"></canvas>
    <script>
    /*
    * Settings
    */
    var settings = {
        particles: {
            length:   500, // maximum amount of particles
            duration:   0.5, // particle duration in sec
            velocity:  75, // particle velocity in pixels/sec
            effect:  0, // play with this for a nice effect
            size:      30, // particle size in pixels
        },
    };

    var settings2 = {
        particles: {
            length:  10000, // maximum amount of particles
            duration:  4.5, // particle duration in sec
            velocity:  50, // particle velocity in pixels/sec
            effect: -0.75, // play with this for a nice effect
            size:       3, // particle size in pixels
        },
    };

/*
 * RequestAnimationFrame polyfill by Erik Möller
 */
    (function(){var b=0;var c=["ms","moz","webkit","o"];for(var a=0;a<c.length&&!window.requestAnimationFrame;++a){window.requestAnimationFrame=window[c[a]+"RequestAnimationFrame"];window.cancelAnimationFrame=window[c[a]+"CancelAnimationFrame"]||window[c[a]+"CancelRequestAnimationFrame"]}if(!window.requestAnimationFrame){window.requestAnimationFrame=function(h,e){var d=new Date().getTime();var f=Math.max(0,16-(d-b));var g=window.setTimeout(function(){h(d+f)},f);b=d+f;return g}}if(!window.cancelAnimationFrame){window.cancelAnimationFrame=function(d){clearTimeout(d)}}}());



    class Point {
        constructor(x, y) {
            this.x = (typeof x !== 'undefined') ? x : 0;
            this.y = (typeof y !== 'undefined') ? y : 0;
        }
        clone() {
            return new Point(this.x, this.y);
        };
        normalize() {
            var length = this.length();
            this.x /= length;
            this.y /= length;
            return this;
        };
        length(l) {
            if (typeof l == 'undefined')
            return Math.sqrt(this.x * this.x + this.y * this.y);
            this.normalize();
            this.x *= l;
            this.y *= l;
            return this;
        };
    }

    class Particle {
        constructor(settingConfig) {
            this.position = new Point();
            this.velocity = new Point();
            this.acceleration = new Point();
            this.age = 0;
            this.settings = settingConfig;
        }
        initialize(x, y, dx, dy) {
            this.position.x = x;
            this.position.y = y;
            this.velocity.x = dx;
            this.velocity.y = dy;
            
            this.acceleration.x = dx * this.settings.particles.effect;
            this.acceleration.y = dy * this.settings.particles.effect;
            this.age = 0;
        };
        update(deltaTime) {
            this.position.x += this.velocity.x * deltaTime;
            this.position.y += this.velocity.y * deltaTime;
            this.velocity.x += this.acceleration.x * deltaTime;
            this.velocity.y += this.acceleration.y * deltaTime;
            this.age += deltaTime;
        };
        draw(context, image) {
            function ease(t) {
                return (--t) * t * t + 1;
            }
            var size = image.width * ease(this.age / this.settings.particles.duration);
            context.globalAlpha = 1 - this.age / this.settings.particles.duration;
            context.drawImage(image, this.position.x - size / 2, this.position.y - size / 2, size, size);
        };
    }

    class ParticlePool {
        constructor(length, settingConfig) {
            // create and populate particle pool
            this.particles = new Array(length);
            this.settings = settingConfig
            for (var i = 0; i < this.particles.length; i++)
                this.particles[i] = new Particle(this.settings);
            
            this.firstActive = 0,
            this.firstFree   = 0,
            this.duration    = this.settings.particles.duration;
        }
        add(x, y, dx, dy) {
            this.particles[this.firstFree].initialize(x, y, dx, dy);
            
            // handle circular queue
            this.firstFree++;
            if (this.firstFree   == this.particles.length) this.firstFree   = 0;
            if (this.firstActive == this.firstFree       ) this.firstActive++;
            if (this.firstActive == this.particles.length) this.firstActive = 0;
        }
        update(deltaTime) {
            var i;
            
            // update active particles
            if (this.firstActive < this.firstFree) {
                for (i = this.firstActive; i < this.firstFree; i++)
                    this.particles[i].update(deltaTime);
            }
            if (this.firstFree < this.firstActive) {
                for (i = this.firstActive; i < this.particles.length; i++)
                    this.particles[i].update(deltaTime);
                for (i = 0; i < this.firstFree; i++)
                    this.particles[i].update(deltaTime);
            }
            
            // remove inactive particles
            while (this.particles[this.firstActive].age >= this.duration && this.firstActive != this.firstFree) {
                this.firstActive++;
                if (this.firstActive == this.particles.length) this.firstActive = 0;
            }
            
        }
        draw(context, image) {
            // draw active particles
            if (this.firstActive < this.firstFree) {
                for (let i = this.firstActive; i < this.firstFree; i++)
                    this.particles[i].draw(context, image);
            }
            if (this.firstFree < this.firstActive) {
                for (let i = this.firstActive; i < this.particles.length; i++)
                    this.particles[i].draw(context, image);
                for (let i = 0; i < this.firstFree; i++)
                    this.particles[i].draw(context, image);
            }
        };
    }

    /*
    * Putting it all together
    */
    (function(canvas) {
        var context = canvas.getContext('2d'),
            particles = new ParticlePool(settings.particles.length, settings),
            particleRate = settings.particles.length / settings.particles.duration, // particles/sec
            time;
        
        // get point on heart with -PI <= t <= PI
        function pointOnHeart(t) {
            return new Point(
                160 * Math.pow(Math.sin(t), 3),
                130 * Math.cos(t) - 50 * Math.cos(2 * t) - 20 * Math.cos(3 * t) - 10 * Math.cos(4 * t) + 25
            );
        }
    
        // creating the particle image using a dummy canvas
        var image = (function() {
            var canvas  = document.createElement('canvas'),
                context = canvas.getContext('2d');
            canvas.width  = settings.particles.size;
            canvas.height = settings.particles.size;
            // helper function to create the path
            function to(t) {
                var point = pointOnHeart(t);
                point.x = settings.particles.size / 2 + point.x * settings.particles.size / 350;
                point.y = settings.particles.size / 2 - point.y * settings.particles.size / 350;
                return point;
            }
            // create the path
            context.beginPath();
            var t = -Math.PI;
            var point = to(t);
            context.moveTo(point.x, point.y);
            while (t < Math.PI) {
            t += 0.01; // baby steps!
            point = to(t);
            context.lineTo(point.x, point.y);
            }
            context.closePath();
            // create the fill
            context.fillStyle = '#e07eaa';
            context.fill();
            // create the image
            var image = new Image();
            image.src = canvas.toDataURL();
            return image;
        })();
        
        // render that thing!
        function render() {
            // next animation frame
            requestAnimationFrame(render);
            
            // update time
            var newTime   = new Date().getTime() / 1000,
                deltaTime = newTime - (time || newTime);
            time = newTime;
            
            // clear canvas
            context.clearRect(0, 0, canvas.width, canvas.height);
            
            // create new particles
            var amount = particleRate * deltaTime;
            for (var i = 0; i < amount; i++) {
            var pos = pointOnHeart(Math.PI - 2 * Math.PI * Math.random());
            var dir = pos.clone().length(settings.particles.velocity);
            particles.add(canvas.width / 2 + pos.x, canvas.height / 2 - pos.y, dir.x, -dir.y);
            }
            
            // update and draw particles
            particles.update(deltaTime);
            particles.draw(context, image);
        }
        
        // handle (re-)sizing of the canvas
        function onResize() {
            canvas.width  = canvas.clientWidth;
            canvas.height = canvas.clientHeight;
        }
        window.onresize = onResize;
        
        // delay rendering bootstrap
        setTimeout(function() {
            onResize();
            render();
        }, 10);
    })(document.getElementById('pinkboard'));

    (function(canvas) {
        var context = canvas.getContext('2d'),
            particles = new ParticlePool(settings2.particles.length, settings2),
            particleRate = 500, // particles/sec
            time;
        
        // get point on heart with -PI <= t <= PI
        function pointOnHeart(t) {
            return new Point(
                160 * Math.pow(Math.sin(t), 3),
                130 * Math.cos(t) - 50 * Math.cos(2 * t) - 20 * Math.cos(3 * t) - 10 * Math.cos(4 * t) + 25
            );
        }
    
        // creating the particle image using a dummy canvas
        var image = (function() {
            var canvas  = document.createElement('canvas'),
                context = canvas.getContext('2d');
            canvas.width  = settings2.particles.size;
            canvas.height = settings2.particles.size;
            // helper function to create the path
            function to(t) {
                var point = pointOnHeart(t);
                point.x = settings2.particles.size/2 + point.x * settings2.particles.size/350;
                point.y = settings2.particles.size/2 - point.y * settings2.particles.size/350;
                return point;
            }
            // create the path
            context.beginPath();
                var t = -Math.PI;
                var point = to(t);
                context.moveTo(point.x, point.y);
                while (t < Math.PI) {
                    if (t < 0.1 || t > 0.1) {
                        t += 0.01; // baby steps!
                    } else {
                        t += 0.1
                    }
                    point = to(t);
                    context.lineTo(point.x, point.y);
                }
            context.closePath();
            
            // create the fill
            context.fillStyle = '#e07eaa';
            context.fill();
            // create the image
            var image = new Image();
            image.src = canvas.toDataURL();
            return image;
        })();
        
        // render that thing!
        function render() {
            // next animation frame
            requestAnimationFrame(render);
            
            // update time
            var newTime   = new Date().getTime() / 1000,
                deltaTime = newTime - (time || newTime);
            time = newTime;
            
            // clear canvas
            context.clearRect(0, 0, canvas.width, canvas.height);
            
            // create new particles
            var amount = particleRate * deltaTime;
            for (var i = 0; i < amount; i++) {
                var pos = pointOnHeart(Math.PI - 2 * Math.PI * Math.random());
                var dir = pos.clone().length(settings2.particles.velocity);
                particles.add(canvas.width / 2 + pos.x, canvas.height / 2 - pos.y, dir.x, -dir.y);
            }
            
            // update and draw particles
            particles.update(deltaTime);
            particles.draw(context, image);
        }
        
        // handle (re-)sizing of the canvas
        function onResize() {
            canvas.width  = canvas.clientWidth;
            canvas.height = canvas.clientHeight;
        }
        window.onresize = onResize;
        
        // delay rendering bootstrap
        setTimeout(function() {
            onResize();
            render();
        }, 10);
    })(document.getElementById('innerLayer'));

        var colours=new Array('#f00', '#f06', '#f0f', '#f6f', '#f39', '#f9c'); // colours of the hearts
        var minisize=10; // smallest size of hearts in pixels
        var maxisize=20; // biggest size of hearts in pixels
        var hearts=100; // maximum number of hearts on screen
        var over_or_under="over"; // set to "over" for hearts to always be on top, or "under" to allow them to float behind other objects

        /*****************************
        JavaScript Love Heart Cursor
        *  (c)2013+ mf2fm web-design *
        *   http://www.mf2fm.com/rv  *
        *  DON'T EDIT BELOW THIS BOX *
        *****************************/
        var x=ox=400;
        var y=oy=300;
        var swide=800;
        var shigh=600;
        var sleft=sdown=0;
        var herz=new Array();
        var herzx=new Array();
        var herzy=new Array();
        var herzs=new Array();
        var kiss=false;

        if (typeof('addRVLoadEvent')!='function') function addRVLoadEvent(funky) {
        var oldonload=window.onload;
        if (typeof(oldonload)!='function') window.onload=funky;
        else window.onload=function() {
            if (oldonload) oldonload();
            funky();
        }
        }

        addRVLoadEvent(mwah);

        function mwah() { if (document.getElementById) {
        var i, heart;
        for (i=0; i<hearts; i++) {
            heart=createDiv("auto", "auto");
            heart.style.visibility="hidden";
            heart.style.zIndex=(over_or_under=="over")?"1001":"0";
            heart.style.color=colours[i%colours.length];
            heart.style.pointerEvents="none";
            if (navigator.appName=="Microsoft Internet Explorer") heart.style.filter="alpha(opacity=75)";
            else heart.style.opacity=0.45;
            heart.appendChild(document.createTextNode(String.fromCharCode(9829)));
            document.body.appendChild(heart);
            herz[i]=heart;
            herzy[i]=false;
        }
        set_scroll();
        set_width();
        herzle();
        }}

        function herzle() {
            var c;
            if (Math.abs(x-ox)>1 || Math.abs(y-oy)>1) {
                ox=x;
                oy=y;
                for (c=0; c<hearts; c++) if (herzy[c]===false) {
                herz[c].firstChild.nodeValue=String.fromCharCode(9829);
                herz[c].style.left=(herzx[c]=x-minisize/2)+"px";
                herz[c].style.top=(herzy[c]=y-minisize)+"px";
                herz[c].style.fontSize=minisize+"px";
                herz[c].style.fontWeight='normal';
                herz[c].style.visibility='visible';
                herzs[c]=minisize;
                break;
                }
            }
            for (c=0; c<hearts; c++) if (herzy[c]!==false) blow_me_a_kiss(c);
            setTimeout("herzle()", 30);
            }

            document.onmousedown=pucker;
            document.onmouseup=function(){clearTimeout(kiss);};

            function pucker() {
            ox=-1;
            oy=-1;
            kiss=setTimeout('pucker()', 100);
        }

        function blow_me_a_kiss(i) {
            herzy[i]-=herzs[i]/minisize+i%2;
            herzx[i]+=(i%5-2)/5;
            if (herzy[i]<sdown-herzs[i] || herzx[i]<sleft-herzs[i] || herzx[i]>sleft+swide-herzs[i]) {
                herz[i].style.visibility="hidden";
                herzy[i]=false;
            }
            else if (herzs[i]>minisize+1 && Math.random()<2.5/hearts) break_my_heart(i);
            else {
                if (Math.random()<maxisize/herzy[i] && herzs[i]<maxisize) herz[i].style.fontSize=(++herzs[i])+"px";
                herz[i].style.top=herzy[i]+"px";
                herz[i].style.left=herzx[i]+"px";
            }
        }

        function break_my_heart(i) {
        var t;
        herz[i].firstChild.nodeValue=String.fromCharCode(9676);
        herz[i].style.fontWeight='bold';
            herzy[i]=false;
        for (t=herzs[i]; t<=maxisize; t++) setTimeout('herz['+i+'].style.fontSize="'+t+'px"', 60*(t-herzs[i]));
        setTimeout('herz['+i+'].style.visibility="hidden";', 60*(t-herzs[i]));
        }

        document.onmousemove=mouse;
        function mouse(e) {
        if (e) {
            y=e.pageY;
            x=e.pageX;
        }
        else {
            set_scroll();
            y=event.y+sdown;
            x=event.x+sleft;
        }
        }

        window.onresize=set_width;
        function set_width() {
        var sw_min=999999;
        var sh_min=999999;
        if (document.documentElement && document.documentElement.clientWidth) {
            if (document.documentElement.clientWidth>0) sw_min=document.documentElement.clientWidth;
            if (document.documentElement.clientHeight>0) sh_min=document.documentElement.clientHeight;
        }
        if (typeof(self.innerWidth)=='number' && self.innerWidth) {
            if (self.innerWidth>0 && self.innerWidth<sw_min) sw_min=self.innerWidth;
            if (self.innerHeight>0 && self.innerHeight<sh_min) sh_min=self.innerHeight;
        }
        if (document.body.clientWidth) {
            if (document.body.clientWidth>0 && document.body.clientWidth<sw_min) sw_min=document.body.clientWidth;
            if (document.body.clientHeight>0 && document.body.clientHeight<sh_min) sh_min=document.body.clientHeight;
        }
        if (sw_min==999999 || sh_min==999999) {
            sw_min=800;
            sh_min=600;
        }
        swide=sw_min;
        shigh=sh_min;
        }

        window.onscroll=set_scroll;
        function set_scroll() {
            if (typeof(self.pageYOffset)=='number') {
                sdown=self.pageYOffset;
                sleft=self.pageXOffset;
            }
            else if (document.body && (document.body.scrollTop || document.body.scrollLeft)) {
                sdown=document.body.scrollTop;
                sleft=document.body.scrollLeft;
            }
            else if (document.documentElement && (document.documentElement.scrollTop || document.documentElement.scrollLeft)) {
                sleft=document.documentElement.scrollLeft;
                sdown=document.documentElement.scrollTop;
            }
            else {
                sdown=0;
                sleft=0;
            }
        }

        function createDiv(height, width) {
        var div=document.createElement("div");
        div.style.position="absolute";
        div.style.height=height;
        div.style.width=width;
        div.style.overflow="hidden";
        div.style.backgroundColor="transparent";
        return (div);
        }
        // ]]>
    </script>
 </BODY>
</HTML>
```