Праздничная анимация фейерверков

Чтобы отметить важное событие , я поспешно объединил анимацию на основе холста HTML.

Несколько проблем, которые я включил:

  • Производительность: Проявляет ли она достаточно плавно на большинстве современных машин? Как я могу сделать его более эффективным?
  • Переносимость /совместимость: Работает ли она корректно во всех современных браузерах (исключая старые версии Internet Explorer)?
  • Моделирование: Является ли это хорошим способом моделирования фейерверков? Есть ли что-то, что я мог бы сделать для повышения реализма?

В то время как мы обычно не говорим «спасибо» в вопросах Stack Exchange, я хотел бы разорвать это правило прямо сейчас и сказать вам большое спасибо Спасибо! всем членам сообщества Code Review .

 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>
90 голосов | спросил 200_success 29 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowMon, 29 Sep 2014 23:00:15 +0400 2014, 23:00:15

4 ответа


31

Производительность

Он отлично работает для меня в Opera и Chrome, но он не работает в Firefox.

Профилирование показывает, что redraw отвечает (большой сюрприз :)), и не так много оптимизировать, не изменяя всю концепцию. Возможны две незначительные оптимизации:

  • сохранить 2 * Math.PI в константе.
  • присвойте context.fillStyle = particle.color; вне цикла частиц (все частицы одного объекта имеют один и тот же цвет).

Но этого недостаточно, чтобы заставить меня бежать в Firefox для меня. Он отлично работает с максимальным количеством фейерверков и 300 частиц (хотя и немного скучно).

Реализм

Есть много вещей, которые можно сделать, чтобы сделать его более реалистичным (с большой нагрузкой и, возможно, большими затратами на производительность), но я думаю, что ваша версия неплоха. На самом деле это не похоже на реальный фейерверк , но это отличное представление. Он выглядит хорошо, и все знают, что он должен быть фейерверком.

Основное отличие от настоящего фейерверка заключается в том, что частицы реальных фейерверков больше не похожи на точки, а на линии (или падает, особенно при падении).

Кроме того, настоящие фейерверки имеют тенденцию появляться в группах взрывов, а не в промежутках времени (потому что более захватывающе видеть одну гигантскую сцену вместо постоянного потока маленького раз). Это не то, на что я бы нацелился в анимации.

Нейминг

  • objects - это немного общий, я думаю. drawableObjects будет немного понятнее, но поскольку он ожидает фейерверка в любом случае, я бы пошел с fireworks.
  • launch также может быть несколько общим. launchFireworks даст понять, что добавляет новый фейерверк (в отличие от запуска анимации или запуска новой частицы).
  • Я использовал fireworks вместо f (и, возможно, particle вместо p)) в redraw.

Разное

  • это хорошо, чтобы жестко закодировать математические факторы, но если вы поместите другие магические числа в полях (длина интервала, количество частиц, количество фейерверков, время всей анимации и т. д.), было бы легче протестировать /изменить конфигурации.
ответил tim 30 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowTue, 30 Sep 2014 02:08:06 +0400 2014, 02:08:06
36
  

Переносимость /совместимость: корректно ли он работает во всех современных браузерах (исключая старые версии Internet Explorer)?

В Safari есть милый бледно-блестящий (не знаю, как его называть) эффект, поскольку полностью разбросанные части исчезают. В Chrome либо нет такого эффекта, либо он настолько слабый, что его не видно. (Я нахожусь на Mac.) (Извините, я не знаю достаточно о анимации /холсте, чтобы понять, почему ...)

  

Моделирование: Это хороший способ имитировать фейерверки? Есть ли что-то, что я мог бы сделать для повышения реализма?

В небольшом представлении фрагмент выглядит великолепно. Если вы переключитесь на полноэкранный режим и посмотрите на него достаточно долго, некоторые незначительные бородавки начинают показывать:

  • В конце взрыва кусочки все движутся вместе, словно застревают на невидимой бумаге и плавают вниз

  • Части, кажется, движутся вниз с постоянной скоростью, а не ускоряются, как будто нет гравитации, или гравитация - это очень неделя. Это не похоже, что они падают.

  • Я предполагаю, что существует ограничение по времени: через какое-то время фейерверк просто остановится, хотя самолет продолжает пролетать. И фейерверк не останавливается грациозно, но в разгаре взрывов, только что замерзших в воздухе.

Незначительные технические вещи

Точки с запятой отсутствуют в конце определений Animation.prototype.start и Animation.prototype.stop. Это действительно, но не соответствует остальной части кода.

Имена греческих переменных - классные ...

var τ = 2 * Math.PI;

... это просто, я не знаю, как их набирать: - /. Было бы проще просто использовать английские слова.

Нижняя строка ...

О, не обманывай! Большое СПАСИБО ВАМ за потрясающий сайт и сообщество! Анонс был неожиданным и остроумным, очень приличным и чистым, удивительным!

ответил janos 29 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowMon, 29 Sep 2014 23:47:21 +0400 2014, 23:47:21
19

У вас всегда должен быть атрибут Alt в тегах img

<img src="http://i.stack.imgur.com/bGZ1m.png" width="80" height="43">
<img src="http://i.stack.imgur.com/bGZ1m.png" width="80" height="43" 
       alt="Toy Airplane - Based on public domain image 
      http://pixabay.com/en/aeroplane-aircraft-airplane-flight-161999/" >

добавлены новые строки для меньшей прокрутки и не нужны

Атрибут Alt может быть чем угодно, но должен описывать изображение, он используется для самых разных вещей, включая программы чтения с экрана для людей с ослабленным зрением.

ответил Malachi 30 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowTue, 30 Sep 2014 00:47:19 +0400 2014, 00:47:19
11

(Я знаю, что я очень поздно праздную ... в любом случае)

Я хотел, чтобы фейерверк сверкал, поэтому я создал объект 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>
ответил oliverpool 23 +03002015-10-23T21:17:10+03:00312015bEurope/MoscowFri, 23 Oct 2015 21:17:10 +0300 2015, 21:17:10

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132