我关心的一些问题包括:它在大多数现代机器上运行是否相当平稳?如何提高效率?
可移植性/兼容性:它是否可以在所有现代浏览器(不包括旧版本的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>
#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.start
和Animation.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
评论
我要悬赏这个问题,只是为了感谢在这个出色站点上工作的每个人!这个问题太棒了!等不及要看评论了!代码审查的美丽之处在于,您可以编写几乎所有内容的工作代码并进行审查:)
我认为一个好的答案应该包含同样聪明的代码段!
它甚至可以在我的iPhone(5c)上流畅播放动画。我认为这应该涵盖您要支持的所有台式机。
错误报告(次要):当炮弹直接在其下方爆炸时,飞机和横幅均不会着火。