为了庆祝一个重要事件,我匆匆整理了一个基于HTML画布的动画。

我关心的一些问题包括:它在大多数现代机器上运行是否相当平稳?如何提高效率?

可移植性/兼容性:它是否可以在所有现代浏览器(不包括旧版本的Internet Explorer)上正常工作?

建模:这是一个模拟烟花的好方法?我能做些什么来增强现实性吗?

虽然我们通常在Stack Exchange问​​题上不说“谢谢”,但我现在想打破该规则并说声谢谢。 !给代码审查社区的所有成员。




 function animate(selector) {
    var $canvas = $(selector);
    var width = $canvas.innerWidth();
    var height = $canvas.innerHeight();

    /* Based on https://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL */
    /* hue ∈ [0, 2π), saturation ∈ [0, 1], lightness ∈ [0, 1] */
    var fromHSL = function fromHSL(hue, saturation, lightness) {
        var c = (1 - Math.abs(2 * lightness - 1)) * saturation;
        var h = 3 * hue / Math.PI;
        var x = c * (1 - (h % 2 - 1));
        var r1 = (h < 1 || 5 <= h) ? c
               : (h < 2 || 4 <= h) ? x
               : 0;
        var g1 = (1 <= h && h < 3) ? c
               : (h < 4) ? x
               : 0;
        var b1 = (3 <= h && h < 5) ? c
               : (2 <= h) ? x
               : 0;
        var m = lightness - c / 2;
        var r = Math.floor(256 * (r1 + m));
        var g = Math.floor(256 * (g1 + m));
        var b = Math.floor(256 * (b1 + m));
        /*
        console.log('hsl(' + hue + ', ' + saturation + ', ' + lightness +
                    ') = rgb(' + r + ', ' + g + ', ' + b + ')');
        */
        return 'rgb(' + r + ', ' + g + ', ' + b + ')';
    };

    var fireworksFactory = function fireworksFactory() {
        var centerX = (0.2 + 0.6 * Math.random()) * width;
        var centerY = (0.1 + 0.4 * Math.random()) * height;
        var color = fromHSL(2 * Math.PI * Math.random(), Math.random(), 0.9);
        return new Firework(centerX, centerY, color);
    };

    var fireworks = [fireworksFactory()];
    var animation = new Animation($canvas, fireworks, fireworksFactory);
    animation.start();
    return animation;
}

function fillBanner(selector) {
    $(selector).text(atob('SGFwcHkgZ3JhZHVhdGlvbiwgQ29kZSBSZXZpZXchIENvbmdyYXR1bGF0aW9ucyE='));
}

//////////////////////////////////////////////////////////////////////

function Animation($canvas, objects, factory) {
    this.canvas = $canvas.get(0);
    this.canvasContext = this.canvas.getContext('2d');
    this.objects = objects;
    this.factory = factory;
}

Animation.prototype.start = function start() {
    var canvas = this.canvas;
    var context = this.canvasContext;
    var objects = this.objects;
    var factory = this.factory;

    var redraw = function redraw() {
        context.clearRect(0, 0, canvas.width, canvas.height);
        for (var f = objects.length - 1; f >= 0; f--) {
            var particles = objects[f].particles;
            for (var p = particles.length - 1; p >= 0; p--) {
                var particle = particles[p];
                context.beginPath();
                context.arc(particle.x, particle.y, particle.size, 0, 2 * Math.PI, false);
                context.fillStyle = particle.color;
                context.fill();
            }
            objects[f].update();
        }
    };

    var launch = function launch() {
        objects.push(factory());
        while (objects.length > 4) {
            objects.shift();
        }
    };

    this.redrawInterval = setInterval(redraw, 25 /* ms */);
    this.factoryInterval = setInterval(launch, 1500 /* ms */);
}

Animation.prototype.stop = function stop() {
    clearInterval(this.redrawInterval);
    clearInterval(this.factoryInterval);
}

//////////////////////////////////////////////////////////////////////

function Firework(centerX, centerY, color) {
    this.centerX = centerX;
    this.centerY = centerY;
    this.color = color;
    this.particles = new Array(500);
    this.Δr = 20;
    this.age = 0;

    var τ = 2 * Math.PI;
    for (var i = 0; i < this.particles.length; i++) {
        this.particles[i] = new Particle(
            this.centerX, this.centerY,
            /* r= */ 0, /* θ= */ τ * Math.random(), /* φ= */ τ * Math.random(),
            /* size= */ 2, color
        );
    }
}

Firework.prototype.update = function update() {
    for (var i = 0; i < this.particles.length; i++) {
        this.particles[i].r += this.Δr;
        this.particles[i].recalcCartesianProjection();

        this.Δr -= 0.00005 * this.Δr * this.Δr;                     // Air resist
        this.particles[i].y += 0.00000008 * this.age * this.age;   // Gravity
        this.particles[i].size *= 0.98;                            // Fade
        this.age++;
    }
};

//////////////////////////////////////////////////////////////////////

function Particle(x, y, r, θ, φ, size, color) {
    this.origX = x;
    this.origY = y;
    this.r = r;
    this.sinθ = Math.sin(θ);
    // this.cosθ = Math.cos(θ);         // Not needed
    this.sinφ = Math.sin(φ);
    this.cosφ = Math.cos(φ);
    this.size = size;
    this.color = color;
    this.recalcCartesianProjection();
}

Particle.prototype.recalcCartesianProjection = function() {
    this.x = this.origX + this.r * this.sinθ * this.cosφ;
    this.y = this.origY + this.r * this.sinθ * this.sinφ;
}; 

 canvas {
    background: black;
    background: linear-gradient(to bottom, black, rgba(0,0,99,0) 400%);
}
div.marquee {
    white-space: nowrap;
    position: absolute;
    top: 60px;
    -webkit-animation: flyby 15s linear infinite;
    animation: flyby 15s linear infinite;
}
@-webkit-keyframes flyby {
    from {
        left: 640px;
    }
    to {
        left: -640px;
    }
}
@keyframes flyby {
    from {
        left: 640px;
    }
    to {
        left: -640px;
    }
}
div.marquee img {
    display: inline-block;
}
div.marquee div {
    display: inline-block;
    position: relative;
    top: -0.8em;
    font: small-caps bold 18px Optima, Futura, sans-serif;
    background: orange;
    padding: 2px 10px;
} 

 <!DOCTYPE HTML>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Animation</title>
    <link rel="stylesheet" type="text/css" href="celebrate.css">
  </head>
  <body>
    <div id="viewport" style="width: 640px; height: 480px;">
      <canvas id="sky" width="640" height="480"></canvas>
      <!-- Based on public domain image
           https://pixabay.com/en/aeroplane-aircraft-airplane-flight-161999/ -->
      <div class="marquee">
        <img src="https://i.stack.imgur.com/bGZ1m.png" width="80" height="43">
        <div id="banner">Using an incompatible browser? No celebration for you.</div>
      </div>
    </div>
    <script type="text/javascript" src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
    <script type="text/javascript" src="celebrate.js"></script>
    <script type="text/javascript">
        $(function() {
            fillBanner('#banner');
            var anim = animate('#sky');
            setTimeout(function() { anim.stop(); }, 60000);
        });
    </script>
  </body>
</html> 




评论

我要悬赏这个问题,只是为了感谢在这个出色站点上工作的每个人!这个问题太棒了!等不及要看评论了!

代码审查的美丽之处在于,您可以编写几乎所有内容的工作代码并进行审查:)

我认为一个好的答案应该包含同样聪明的代码段!

它甚至可以在我的iPhone(5c)上流畅播放动画。我认为这应该涵盖您要支持的所有台式机。

错误报告(次要):当炮弹直接在其下方爆炸时,飞机和横幅均不会着火。

#1 楼

性能

在Opera和Chrome中对我来说效果很好,但在Firefox中却存在问题。

分析显示redraw是负责任的(大惊喜:)),在不更改整个概念的情况下,没有太多要优化的地方。两个较小的优化可能是:将2 * Math.PI保存为常量。
在粒子循环外部分配context.fillStyle = particle.color;(一个对象的所有粒子都具有相同的颜色)。

但这还不足以使它在Firefox中对我无延迟运行。最多可播放3个烟火和300个粒子(尽管有点无聊),效果很好。

现实主义

有很多事情可以使它变得更加现实(需要大量工作,并且可能会增加性能成本),但是我认为您的版本非常好。它实际上看起来并不像真正的烟花,但这是一个很好的表示。它看起来不错,而且每个人都知道它应该是烟花。

与真实烟花的主要区别在于,真实烟花的粒子大部分时间看起来不像点,而是像线(或下降,尤其是跌落时)。

另外,真实的烟花往往以爆炸的形式出现,而不是按时间间隔出现(因为看到一个巨大的场景比源源不断的小场景更令人兴奋)。不过,这并不是我希望在动画中实现的目标。

命名





objects有点通用。 drawableObjects会更清晰一些,但是无论如何它都会期望有烟花,我会选择fireworks

launch也可能有点通用。 launchFireworks会清楚地表明它添加了新的烟火(而不是说启动动画或发射新的粒子)。
我会在fireworks中使用f代替particle(可能是p而不是redraw)。 br />
其他


可以对数学因子进行硬编码,但是如果将其他魔术数字放在字段中(时间间隔长度,粒子数量,烟火数量,整个动画时间等),则测试/更改配置会更容易。 >

#2 楼


可移植性/兼容性:是否可以在所有现代浏览器(不包括旧版本的Internet Explorer)上正常工作?不知道怎么称呼)效果,因为散布的碎片逐渐消失了。在Chrome浏览器中,要么没有这种效果,要么太微弱以至于看不到。 (我在Mac上。)(对不起,我对动画/画布的了解不足,无法了解为什么...)


建模:这很好吗?模拟烟花的方式?我可以做些什么来增强真实感吗?如果切换到全屏模式并观察足够长的时间,就会出现一些小的疣:


在爆炸结束时,所有部件都一起移动,就像卡在了看不见的纸并向下漂浮
这些碎片似乎以恒定的速度向下移动而不是加速,好像没有重力,或者重力非常大。看起来好像真的没有落下
我想是有时间限制的:经过一段时间后,烟花一直停下来,尽管飞机一直在飞行。而且烟花并不能优雅地停下来,而是在爆炸中,只是冻结在空中。 Animation.prototype.startAnimation.prototype.stop的这是有效的,但与其余代码不一致。

希腊变量名称很酷...


var τ = 2 * Math.PI;



...就是这样,我不知道如何输入它们:-/。仅使用英文单词会更简单。

最重要的是...

哦,别管这些了!非常感谢您为这个令人惊叹的网站和社区!公告是出乎意料的,机智的,非常合适,而且真是太棒了!

评论


\ $ \ begingroup \ $
我打赌Safari中的“闪闪发光的效果”是某种抗锯齿或仿射变换的伪像(是的,我也看到了)。我知道Safari的渲染行为与Chrome(和IE)的行为有很大不同,但我不知道具体细节。也就是说,效果几乎比bug多。它非常适合整体效果。但是没关系!谢谢大家,恭喜!
\ $ \ endgroup \ $
– Flaambino
2014年9月29日在19:58

#3 楼

您应该始终在img标记中添加一个Alt属性。滚动而不是必需的

Alt属性可以是任何东西,但可以描述图像,它用于许多不同的事物,包括视障用户的屏幕阅读器。

评论


\ $ \ begingroup \ $
尽管您应该始终具有alt属性,但您不应始终具有其中的值。如果价值不是对网站用户有价值的内容,那么实际上是有害的。在这个例子中,我会说这是有害的。为了使此属性有用,我建议使用“飞机”值
\ $ \ endgroup \ $
– DA。
2014-09-30 4:09



\ $ \ begingroup \ $
@DA。我同意,我只是在其中添加了半相关的内容,但是少则更好,尤其是在移动的内容上。但是我不同意,它不应该总是有一个值,它应该总是有一个值,以便它符合标准。
\ $ \ endgroup \ $
–马拉奇♦
2014年9月30日4:17在

\ $ \ begingroup \ $
如果图像纯粹是装饰性的(即,不是页面内容的一部分),则它实际上应该具有一个空的alt属性:sitepoint.com/…
\ $ \ endgroup \ $
– DA。
2014年9月30日6:30在

\ $ \ begingroup \ $
W3标准规定,当图像“用于在列表,水平线或其他类似装饰中创建项目符号”时,该图像与作为显示站点主要部分的视觉对象不一样,在这种情况下,正确的属性值为“飞机”,因为横幅已经在html / javascript中进行了文本化处理。 W3标准表示,在使用图像进行格式化时,请将属性留空,而飞过屏幕的飞机未进行格式化。
\ $ \ endgroup \ $
–马拉奇♦
2014-09-30 12:39

\ $ \ begingroup \ $
在此示例中,它是主观的,但是是的,在这种特殊情况下,“飞机”可能适用。我的观点只是,尽管您应该始终具有alt属性,但不一定必须始终具有值。
\ $ \ endgroup \ $
– DA。
2014-09-30 14:02

#4 楼

(无论如何,我知道现在庆祝还很晚)

我希望烟花闪烁,所以我创建了一个Color对象。当烟花“足够老”时,粒子会开始使用随机的不透明性开始闪烁:-)

我还对动画的结尾提到了@janos的评论:现在的最后一个烟花有一对秒消失。




 function animate(selector) {
    var $canvas = $(selector);
    var width = $canvas.innerWidth();
    var height = $canvas.innerHeight();

    var fireworksFactory = function fireworksFactory() {
        var centerX = (0.2 + 0.6 * Math.random()) * width;
        var centerY = (0.1 + 0.4 * Math.random()) * height;
        var color = new Color(2 * Math.PI * Math.random(), Math.random(), 0.9);
        return new Firework(centerX, centerY, color);
    };

    var fireworks = [fireworksFactory()];
    var animation = new Animation($canvas, fireworks, fireworksFactory);
    animation.start();
    return animation;
}

function fillBanner(selector) {
    $(selector).text(atob('SGFwcHkgZ3JhZHVhdGlvbiwgQ29kZSBSZXZpZXchIENvbmdyYXR1bGF0aW9ucyE='));
}

//////////////////////////////////////////////////////////////////////

function Animation($canvas, objects, factory) {
    this.canvas = $canvas.get(0);
    this.canvasContext = this.canvas.getContext('2d');
    this.objects = objects;
    this.factory = factory;
}

Animation.prototype.start = function start() {
    var canvas = this.canvas;
    var context = this.canvasContext;
    var objects = this.objects;
    var factory = this.factory;

    var redraw = function redraw() {
        context.clearRect(0, 0, canvas.width, canvas.height);
        for (var f = objects.length - 1; f >= 0; f--) {
            var particles = objects[f].particles;
            for (var p = particles.length - 1; p >= 0; p--) {
                var particle = particles[p];
                context.beginPath();
                context.arc(particle.x, particle.y, particle.size, 0, 2 * Math.PI, false);
                context.fillStyle = particle.color;
                context.fill();
            }
            objects[f].update();
        }
    };

    var launch = function launch() {
        objects.push(factory());
        while (objects.length > 4) {
            objects.shift();
        }
    };

    this.redrawInterval = setInterval(redraw, 25 /* ms */);
    this.factoryInterval = setInterval(launch, 1500 /* ms */);
}

Animation.prototype.stop = function stop() {
    clearInterval(this.factoryInterval);
    setTimeout(function() { clearInterval(this.redrawInterval); }, 3000);
}

//////////////////////////////////////////////////////////////////////

function Firework(centerX, centerY, color) {
    this.centerX = centerX;
    this.centerY = centerY;
    this.color = color;
    this.particles = new Array(500);
    this.Δr = 20;
    this.age = 0;
    this.color = color

    var τ = 2 * Math.PI;
    for (var i = 0; i < this.particles.length; i++) {
        this.particles[i] = new Particle(
            this.centerX, this.centerY,
            /* r= */ 0, /* θ= */ τ * Math.random(), /* φ= */ τ * Math.random(),
            /* size= */ 2, color.rgb()
        );
    }
}

Firework.prototype.update = function update() {
    for (var i = 0; i < this.particles.length; i++) {
        this.particles[i].r += this.Δr;
        this.particles[i].recalcCartesianProjection();

        this.Δr -= 0.00005 * this.Δr * this.Δr;                     // Air resist
        this.particles[i].y += 0.00000008 * this.age * this.age;   // Gravity
        this.particles[i].size *= 0.98;                            // Fade
        this.age++;
        if(this.age > 10000){
            // Let the particles sparkle after some time
            this.particles[i].color = this.color.rgba();
        }
    }
};

//////////////////////////////////////////////////////////////////////

function Color(hue, saturation, lightness) {
    /* Based on https://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL */
    /* hue ∈ [0, 2π), saturation ∈ [0, 1], lightness ∈ [0, 1] */
    var c = (1 - Math.abs(2 * lightness - 1)) * saturation;
    var h = 3 * hue / Math.PI;
    var x = c * (1 - (h % 2 - 1));
    var r1 = (h < 1 || 5 <= h) ? c
           : (h < 2 || 4 <= h) ? x
           : 0;
    var g1 = (1 <= h && h < 3) ? c
           : (h < 4) ? x
           : 0;
    var b1 = (3 <= h && h < 5) ? c
           : (2 <= h) ? x
           : 0;
    var m = lightness - c / 2;
    var r = Math.floor(256 * (r1 + m));
    var g = Math.floor(256 * (g1 + m));
    var b = Math.floor(256 * (b1 + m));
    /*
    console.log('hsl(' + hue + ', ' + saturation + ', ' + lightness +
                ') = rgb(' + r + ', ' + g + ', ' + b + ')');
    */
    this.r = r;
    this.g = g;
    this.b = b;
}

Color.prototype.rgb = function() {
    return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';
};

Color.prototype.rgba = function() {
    var opacity = Math.min(1, Math.random()*5);
    return 'rgba(' + this.r + ', ' + this.g + ', ' + this.b + ', ' + opacity + ')';
};

//////////////////////////////////////////////////////////////////////

function Particle(x, y, r, θ, φ, size, color) {
    this.origX = x;
    this.origY = y;
    this.r = r;
    this.sinθ = Math.sin(θ);
    // this.cosθ = Math.cos(θ);         // Not needed
    this.sinφ = Math.sin(φ);
    this.cosφ = Math.cos(φ);
    this.size = size;
    this.color = color;
    this.recalcCartesianProjection();
}

Particle.prototype.recalcCartesianProjection = function() {
    this.x = this.origX + this.r * this.sinθ * this.cosφ;
    this.y = this.origY + this.r * this.sinθ * this.sinφ;
}; 

 canvas {
    background: black;
    background: linear-gradient(to bottom, black, rgba(0,0,99,0) 400%);
}
div.marquee {
    white-space: nowrap;
    position: absolute;
    top: 60px;
    -webkit-animation: flyby 15s linear infinite;
    animation: flyby 15s linear infinite;
}
@-webkit-keyframes flyby {
    from {
        left: 640px;
    }
    to {
        left: -640px;
    }
}
@keyframes flyby {
    from {
        left: 640px;
    }
    to {
        left: -640px;
    }
}
div.marquee img {
    display: inline-block;
}
div.marquee div {
    display: inline-block;
    position: relative;
    top: -0.8em;
    font: small-caps bold 18px Optima, Futura, sans-serif;
    background: orange;
    padding: 2px 10px;
} 

 <!DOCTYPE HTML>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Animation</title>
    <link rel="stylesheet" type="text/css" href="celebrate.css">
  </head>
  <body>
    <div id="viewport" style="width: 640px; height: 480px;">
      <canvas id="sky" width="640" height="480"></canvas>
      <!-- Based on public domain image
           https://pixabay.com/en/aeroplane-aircraft-airplane-flight-161999/ -->
      <div class="marquee">
        <img src="https://i.stack.imgur.com/bGZ1m.png" width="80" height="43">
        <div id="banner">Using an old version of Internet Explorer? No celebration for you.</div>
      </div>
    </div>
    <script type="text/javascript" src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
    <script type="text/javascript" src="celebrate.js"></script>
    <script type="text/javascript">
        $(function() {
            fillBanner('#banner');
            var anim = animate('#sky');
            setTimeout(function() { anim.stop(); }, 60000);
        });
    </script>
  </body>
</html> 




评论


\ $ \ begingroup \ $
我正在使用chrome,看不到任何烟花...我在IE中运行,能够看到烟花,但没有收到“不庆祝”消息
\ $ \ endgroup \ $
–马拉奇♦
17年8月3日在15:32



\ $ \ begingroup \ $
Chrome给我警告,该网站正在尝试加载不安全的脚本,现在可以看到烟花。我看到褪色,但没有闪闪发光。
\ $ \ endgroup \ $
–马拉奇♦
17年8月3日在15:41

\ $ \ begingroup \ $
@Malachi感谢您让我来!它应该可以再次工作(到处都是https)
\ $ \ endgroup \ $
– oliverpool
17年8月7日在12:47