鼠标跟随气泡生成动画

HTML代码部分

<html>
    <head>
        <title>鼠标跟随代码</title>
        <meta content="text/html; charset=utf8" http-equiv="Content-Type">
    </head>
    <body>
        <form class="controls">
            <label>
                # particles:
                <input type="range" id="particles" min="2" max="10" step="1" />
                <output></output>
            </label>
        </form>
        <div class="fullscreen">
            <canvas class="" id="canvas"></canvas>
        </div>
    </body>
</html>

CSS代码部分

html,body {
    margin: 0;
    padding: 0;
}
canvas {
    display: block;
    -webkit-user-select: none;
    -moz-user-select: none;
    user-select: none;
}
body {
    font-family: sans-serif;
}
.controls {
    position: absolute;
    top: 0;
    right: 0;
    padding: 0.75em 1em;
    background-color: rgba(0, 0, 0, 0.75);
    color: white;
    z-index: 10;
}
.dark {
    background-color: black;
}
.dark .controls {
    background-color: rgba(255, 255, 255, 0.75);
    color: black;
}
.dark legend {
    color: green;
}
.fullscreen {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
}
fieldset {
    border: none;
    margin: 0;
    padding: 0;
}
legend {
    padding: 0;
    margin: 0;
    margin-bottom: 0.5em;
    color: yellow;
    display: inline;
}
label {
    padding-right: 0.5em;
}

JS代码部分

function fluent(fn) {
    return function() {
        fn.apply(this, arguments);
        return this;
    };
}

var util = {
    random: function(minOrMax, max) {
        if (arguments.length === 1) {
            return minOrMax * Math.random();
        }

        return minOrMax + (Math.random() * (max - minOrMax));
    },

    norm: function(value, min, max) {
        return (value - min) / (max - min);
    },

    lerp: function(norm, min, max) {
        return (max - min) * norm + min;
    },

    map: function(value, sourceMin, sourceMax, destMin, destMax) {
        return this.lerp(this.norm(value, sourceMin, sourceMax), destMin, destMax);
    },

    between: function(value, min, max) {
        return (value > min) && (value < max);
    },

    inBounds: function(x, y, width, height) {
        return this.between(x, 0, width) && this.between(y, 0, height);
    },

    clamp: function(value, min, max) {
        return Math.min(Math.max(value, min), max);
    },

    invoke: function(fn, args) {
        return function(obj) {
            return obj[fn].apply(obj, args);
        };
    }
};

function Vector(x, y) {
    this.x = x || 0;
    this.y = y || 0;
}
Vector.prototype = {
    constructor: Vector,

    getX: function() {
        return this.x;
    },

    setX: fluent(function(x) {
        this.x = x;
    }),

    getY: function() {
        return this.y;
    },

    setY: fluent(function(y) {
        this.y = y;
    }),

    getAngle: function() {
        return Math.atan2(this.y, this.x);
    },

    setAngle: fluent(function(angle) {
        var magnitude = this.getMagnitude();
        this.x = Math.cos(angle) * magnitude;
        this.y = Math.sin(angle) * magnitude;
    }),

    getMagnitude: function() {
        return Math.sqrt(
            Math.pow(this.x, 2) + Math.pow(this.y, 2)
        );
    },

    setMagnitude: fluent(function(magnitude) {
        var angle = this.getAngle();
        this.x = Math.cos(angle) * magnitude;
        this.y = Math.sin(angle) * magnitude;
    })
};


Vector.add = function(a, b) {
    return new Vector(
        a.getX() + b.getX(),
        a.getY() + b.getY()
    );
};

Vector.subtract = function(a, b) {
    return new Vector(
        a.getX() - b.getX(),
        a.getY() - b.getY()
    );
};

Vector.multiply = function(vector, value) {
    return new Vector(
        vector.getX() * value,
        vector.getY() * value
    );
};

Vector.divide = function(vector, value) {
    return new Vector(
        vector.getX() / value,
        vector.getY() / value
    );
};

function Particle(x, y, speed, direction, grav) {
    this.position = new Vector(x, y);
    this.velocity = new Vector().setMagnitude(speed).setAngle(direction);
    this.gravity = new Vector(0, grav || 0);
    this.mass = 1;
    this.radius = 0;
    this.bounce = -1;
}
Particle.prototype = {
    constructor: Particle,

    update: fluent(function() {
        this.position = Vector.add(this.position, this.velocity);
        this.accelerate(this.gravity);
    }),

    accelerate: fluent(function(acceleration) {
        this.velocity = Vector.add(this.velocity, acceleration);
    }),

    getPositionX: function() {
        return this.position.getX();
    },

    setPositionX: fluent(function(x) {
        this.position.setX(x);
    }),

    getPositionY: function() {
        return this.position.getY();
    },

    setPositionY: fluent(function(y) {
        this.position.setY(y);
    }),

    angleTo: function(p2) {
        return Math.atan2(
            p2.getPositionY() - this.getPositionY(),
            p2.getPositionX() - this.getPositionX()
        );
    },

    distanceTo: function(p2) {
        var dx = p2.getPositionX() - this.getPositionX();
        var dy = p2.getPositionY() - this.getPositionY();

        return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
    },

    gravitateTo: fluent(function(p2) {
        var grav = new Vector();
        var dist = this.distanceTo(p2);

        grav.setMagnitude(p2.mass / Math.pow(dist, 2));
        grav.setAngle(this.angleTo(p2));
        this.velocity = Vector.add(this.velocity, grav);
    })
};

function Canvas(elem, width, height) {
    this.width = elem.width = (width * Canvas.pixelRatio);
    this.height = elem.height = (height * Canvas.pixelRatio);
    elem.style.height = "100%";
    elem.style.width = "100%";
    this.context = elem.getContext("2d");
    this.context.scale(Canvas.pixelRatio, Canvas.pixelRatio);
    this.setSmoothing(false);
}
Canvas.prototype = {
    constructor: Canvas,

    setSmoothing: fluent(function(value) {
        this.context.webkitImageSmoothingEnabled = value;
        this.context.mozImageSmoothingEnabled = value;
        this.context.msImageSmoothingEnabled = value;
        this.context.imageSmoothingEnabled = value;
    }),

    clear: fluent(function(width, height) {
        width = width || this.width;
        height = height || this.height;

        this.context.clearRect(0, 0, width, height);
    }),

    rect: fluent(function(x, y, width, height) {
        x = x || 0;
        y = y || 0;
        width = width || this.width;
        height = height || this.height;

        this.context.beginPath();
        this.context.rect(x, y, width, height);
    }),

    circle: fluent(function(x, y, radius) {
        this.context.beginPath();
        this.context.arc(x, y, radius, Math.PI * 2, false);
    }),

    fill: fluent(function(color) {
        color = color || "#000000";
        this.context.fillStyle = color;
        this.context.fill();
    }),

    stroke: fluent(function(color) {
        color = color || "#000000";
        this.context.strokeStyle = color;
        this.context.stroke();
    })
};

Canvas.pixelRatio = window.devicePixelRatio || 1;

function each(collection, iter) {
    for (var i = 0; i < collection.length; i++) {
        iter(collection[i]);
    }
}

function filter(collection, predicate) {
    var out = [];
    for (var i = 0; i < collection.length; i++) {
        if (predicate(collection[i])) {
            out.push(collection[i]);
        }
    }
    return out;
}

function hsl(h, s, l, a) {
    a = a === undefined ? 1 : a;
    return "hsla(" + h + "," + s + "%," + l + "%," + a + ")";
}

var NUM_PARTICLES = 6 / Canvas.pixelRatio;
var hue = 48;
var saturation = 33;


function FadingParticle(x, y) {
    this.particle = new Particle(x, y, util.random(1, 2), util.random(Math.PI * 2), -0.1);
    this.particle.radius = util.random(3, 5);
    this.hsla = [hue, saturation, util.random(50, 100), 1];
}
FadingParticle.prototype = {
    constructor: FadingParticle,

    render: function(canvas) {
        this.particle.update();
        this.updateColor();

        canvas
            .circle(this.particle.getPositionX(), this.particle.getPositionY(), this.particle.radius)
            .fill(hsl.apply(null, this.hsla));
    },

    updateColor: function() {
        this.hsla[3] *= 0.99;
    },

    isAlive: function() {
        var notTransparent = this.hsla[3] > 0.01;
        var visible = (this.particle.getPositionY() - this.particle.radius) > 0;
        return notTransparent && visible;
    }
};


function createExplosion(x, y) {
    var explosion = [];
    for (var i = 0; i < NUM_PARTICLES; i++) {
        explosion.push(new FadingParticle(x, y));
    }
    return explosion;
}

window.onload = function() {
    var width = window.innerWidth;
    var height = window.innerHeight;
    var canvas = new Canvas(document.querySelector("#canvas"), width, height);

    particles = createExplosion(width / 2, height / 2);
    var renderParticles = util.invoke("render", [canvas]);
    var isAlive = util.invoke("isAlive");

    function render() {
        canvas.clear();

        each(particles, renderParticles);
        particles = filter(particles, isAlive);

        requestAnimationFrame(render);
    }
    render();

    function explode(e) {
        e = e.touches ? e.touches[0] : e;
        particles = particles.concat(createExplosion(e.clientX, e.clientY));
    }


    document.addEventListener("touchmove", explode);
    document.addEventListener("mousemove", explode);
    document.addEventListener("click", explode);
    document.addEventListener("touchend", explode);

    var output = document.querySelector("output");
    var slider = document.querySelector("#particles");
    slider.value = output.innerHTML = NUM_PARTICLES;

    slider.addEventListener("input", function(e) {
        NUM_PARTICLES = output.innerHTML = this.valueAsNumber;
    });

};

若方向向上:

this.gravity = new Vector(0, grav || 0);

若方向向下:

this.gravity = new Vector(grav, 1 || grav);

若方向向左:

this.gravity = new Vector(-1, grav || -1);

若方向向右:

this.gravity = new Vector(1, grav || 1);


转载于:https://my.oschina.net/elaine96/blog/548325

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值