試みました。
JavaScript
(function(win) { var requestAnimationFrame = win.requestAnimationFrame || win.mozRequestAnimationFrame || win.webkitRequestAnimationFrame || win.msRequestAnimationFrame; win.requestAnimationFrame = requestAnimationFrame; })(this); (function(win, doc, $) { "use strict" function Bubble(ctx, x, y, r) { var SWING_LEVEL = 5, model = { top: y, left: x, size: 0, color: "rgba(255, 255, 255, " + (0.4 + Math.random() / 4) + ")", innerRate: 0.4 + Math.random() / 4, param: { ctx: ctx, x: x, y: y, r: r } }; this.model = model; this.$model = $(this.model); this.ctx = ctx; this.swingLevel = SWING_LEVEL * Math.random(); this.born(); this.flow(); this.draw(); } Bubble.prototype._progress = function(_, per) { this.model.left = this.model.left + Math.sin(2 * Math.PI * per * this.swingLevel); }; Bubble.prototype._compleate = function() { this.model.size = 0; this.model.top = this.model.param.y; this.born(); this.flow(); }; Bubble.prototype.draw = function() { var ctx = this.ctx; ctx.save(); ctx.globalCompositeOperation = "lighter"; ctx.fillStyle = this.model.color; ctx.beginPath(); ctx.arc(this.model.left, this.model.top, this.model.size, 0, Math.PI * 2, false); ctx.fill(); ctx.save(); ctx.globalCompositeOperation = "destination-out"; ctx.fillStyle = "#000"; ctx.beginPath(); ctx.arc(this.model.left, this.model.top - (1 - this.model.size) * this.model.innerRate, this.model.size * this.model.innerRate, 0, Math.PI * 2, false); ctx.fill(); ctx.restore(); ctx.restore(); }; Bubble.prototype.born = function() { var SINK_DISTANCE = 50, DURATION = 500; this.$model.animate({ size: this.model.param.r, top: this.model.top + SINK_DISTANCE * Math.random() | 0 }, DURATION * Math.random() | 0); }; Bubble.prototype.flow = function() { var EASING_TYPE = "easeInCubic", DURATION = 4000; this.$model.animate({ top: -this.model.param.r }, { easing: EASING_TYPE, progress: this._progress.bind(this), complete: this._compleate.bind(this), duration: DURATION - (DURATION / 2 * Math.random() | 0) }); }; function BubbleManager(ctx, width, height, bottomLimit, maxSize, length) { var bubbles = []; for (var i = 0; i < length; ++i) { bubbles.push(new Bubble( ctx, width * Math.random(), height - bottomLimit * Math.random(), maxSize * Math.random() )); } this.length = length; this.bubbles = bubbles; } BubbleManager.prototype.draw = function() { for (var i = 0, length = this.length; i < length; ++i) { this.bubbles[i].draw(); } } var canvas = doc.getElementById("canvas"), ctx = canvas.getContext("2d"), canvasWidth = win.innerWidth, canvasHeight = win.innerHeight; _init(); function _init() { var BOTTOM_LIMIT = canvasHeight, BUBBLE_MAX_SIZE = 5, BUBBLE_LENGTH = canvasWidth * 2 | 0; var bubbleManager = new BubbleManager( ctx, canvasWidth, canvasHeight, BOTTOM_LIMIT, BUBBLE_MAX_SIZE, BUBBLE_LENGTH ); _setCnavasSize(); requestAnimationFrame(_draw); function _draw() { _setCnavasSize(); bubbleManager.draw(); requestAnimationFrame(_draw); } } function _setCnavasSize() { canvas.width = canvasWidth; canvas.height = canvasHeight; } })(this, document, $);