Гиперпространство на JavaScript

Хабровчане! С днем космонавтики!



В одном проекте, приуроченном к сегодняшнему празднику дизайнерами была поставлена задача создать имитацию гиперпространства. Немного поразмыслив решил что правильнее будет использовать Canvas элемент — для SVG достаточно много элементов, да и поддержка среди браузеров не такая хорошая, для видео слишком большой фон, а значит слишком большой размер файла и долгая загрузка. Canvas, к слову, тоже не идеальный вариант — он сильно нагружает процессор и забирает относительно много оперативной памяти. Но все же…

Запись на полях
В коде будет использовать функция reqestAnimFrame нормализующая работу requestAnimationFrame в разных браузерах, а та же простая функция генерации случайно целого числа. Вот их исходный код:
        window.requestAnimFrame = (function(){ 
            return  window.requestAnimationFrame       || 
                    window.webkitRequestAnimationFrame || 
                    window.mozRequestAnimationFrame    || 
                    window.oRequestAnimationFrame      || 
                    window.msRequestAnimationFrame     || 
                    function(/* function */ callback, /* DOMElement */ element){ 
                        window.setTimeout(callback, 1000 / 60); 
                    }; 
        })();
 
        function getRandomInt(min, max){ 
            return Math.floor( Math.random() * (max - min + 1) ) + min; 
        }


Подход 1. Наложение масштабированной картинки.

В дизайне была картинка звездного неба, которую я и решил использовать. Взял, загрузил, наложил на Canvas. Потом наложил с увеличением еще на один пиксель. Потом еще. Картинка получается красивая, но как-либо настраивать её не получится.

Полный код и демонстрация на jsfiddle.

В коде комментировать нечего, поэтому просто код.

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

Подход 2. Боевой.

Было решено рисовать каждую звезду и шлейф от нее отдельно.

Полный код и демонстрация на jsfiddle. Движение повешено на mousemove событие.

Итак, создаем массив звезд, генерируем для них начальные значения. Здесь x и y, понятное дело, координаты звезды, currentLife — показатель текущей длина шлейфа от звезды, nx, ny и life используются для реинициализации звезды, после остановки. color — один из вариантов в массиве colors. В принципе можно было сделать вообще любой цвет, но особенность в ограничении количества доступных цветов нам пригодится позже. Массива два, так в момент затухания необходимо показывать двигающиеся и неподвижные звезды одновременно. Конечно это можно (и, наверное, даже нужно) сделать через один массив с отдельным свойством у звезды, но от этого зависит дальнейшая логика и поэтому мне лень все переписывать.

      var colors = ["white","rgb(200,200,255)"];  
  
        function newStar(){  
            var life = getRandomInt(50,150);  
            var dx = getRandomInt(0,canvas.width);  
            var dy = getRandomInt(0,canvas.height);  
            return {  
                x : dx,  
                y : dy,  
                nx : dx,  
                ny : dy,  
                life : life,  
                currentLife : life,  
                color : colors[getRandomInt(0,1)]  
            };  
        }  
  
        var stars = [];  
        var finStars = [];  
        var maxStars = 350;  
        for(var i = 0; i < maxStars; i++){  
            finStars.push(newStar());  
        }  


Теперь поговорим про отображение звезды. Здесь у нас включается простая математика:



Думаю не надо объяснять что dx относится к dy так же, как и ax к ay. Если взять dx равным значению currentLife, то dy = currentLife * ( y — cy ) / ( x — cx ). Кроме этого у каждой звезды есть два состояния — когда шлейф растет и когда убывает. Реализовать это достаточно просто через 4 значения: 2 постоянных и 2 переменных. Рисуем от (var1 > const1? var1: const1) до (var2 < const2? var2: const2). Получаем сначала растущую, а потом затухающую звезду.



Остается все это посчитать:

                        var x = stars[j].x, // (x,y) - это const1  
                            y = stars[j].y,  
                            dx = cx - stars[j].x,   
                            dy = cy - stars[j].y;  
                        if ( Math.abs(dx) > Math.abs(dy) ){  
                            var xLife = dx > 0 ? stars[j].life : - stars[j].life, // (xLife, yLife) - const2. Вообще star.life это вся продолжительность "жизни" звезды  
                                yLife = xLife * dy / dx,  
                                xCur = dx > 0 ? - stars[j].currentLife :  stars[j].currentLife, // (xCur,yCur) -var1  
                                yCur = xCur * dy / dx,  
                                xLast = dx > 0 ? xCur + stars[j].life : xCur - stars[j].life, // (xLast,yLast) - var2  
                                yLast = xLast * dy / dx,  
                                mod = "x";  
                        } else {  
                            var yLife = dy > 0 ? stars[j].life : - stars[j].life,  
                                xLife = yLife * dx / dy,  
                                yCur = dy > 0 ? - stars[j].currentLife :  stars[j].currentLife,  
                                xCur = yCur * dx / dy,  
                                yLast = dy > 0 ? yCur + stars[j].life : yCur - stars[j].life,  
                                xLast = yLast * dx / dy,  
                                mod = "y";  
                        }  
   
                        if(dx > 0 && dy > 0)  
                        {  
                            var qx = x - ( xLife < xLast ? xLife : xLast);  
                            var qy = y - ( yLife < yLast ? yLife : yLast);  
                            ctx.moveTo( qx < cx ? qx : cx, qy < cy ? qy : cy);  
                            var sx = x - ( xCur > 0 ? xCur : 0);  
                            var sy = y - ( yCur > 0 ? yCur : 0);  
                            ctx.lineTo( sx < cx ? sx : cx, sy < cy ? sy : cy);  
                            if ( mod == "x"){  
                                ctx.lineTo( qx < cx ? qx : cx, (qy < cy ? qy : cy) + 2);  
                            } else {  
                                ctx.lineTo( (qx < cx ? qx : cx) + 2, qy < cy ? qy : cy);  
                            }  
                            ctx.lineTo( qx < cx ? qx : cx, qy < cy ? qy : cy);  
                            ctx.closePath();  
                            stars[j].nx = sx < cx ? sx : cx;  
                            stars[j].ny = sy < cy ? sy : cy;  
                        }  
                        if(dx < 0 && dy < 0)  
                        {  
                            var qx = x - ( xLife > xLast ? xLife : xLast);  
                            var qy = y - ( yLife > yLast ? yLife : yLast);  
                            ctx.moveTo( qx > cx ? qx : cx, qy > cy ? qy : cy);  
                            var sx = x - ( xCur < 0 ? xCur : 0);  
                            var sy = y - ( yCur < 0 ? yCur : 0);  
                            ctx.lineTo( sx > cx ? sx : cx, sy > cy ? sy : cy);  
                            if ( mod == "x" ){  
                                ctx.lineTo( qx > cx ? qx : cx, (qy > cy ? qy : cy) + 2);  
                            } else {  
                                ctx.lineTo( (qx > cx ? qx : cx) + 2, qy > cy ? qy : cy);  
                            }  
                            ctx.lineTo( qx > cx ? qx : cx, qy > cy ? qy : cy);  
                            ctx.closePath();  
                            stars[j].nx = sx > cx ? sx : cx;  
                            stars[j].ny = sy > cy ? sy : cy;  
                        }  
                        if(dx < 0 && dy > 0)  
                        {  
                            var qx = x - ( xLife > xLast ? xLife : xLast);  
                            var qy = y - ( yLife < yLast ? yLife : yLast);  
                            ctx.moveTo( qx > cx ? qx : cx, qy < cy ? qy : cy);  
                            var sx = x - ( xCur < 0 ? xCur : 0);  
                            var sy = y - ( yCur > 0 ? yCur : 0);  
                            ctx.lineTo( sx > cx ? sx : cx, sy < cy ? sy : cy);  
                            if ( mod == "x" ){  
                                ctx.lineTo( qx > cx ? qx : cx, (qy < cy ? qy : cy) + 2);  
                            } else {  
                                ctx.lineTo( (qx > cx ? qx : cx) + 2, qy < cy ? qy : cy);  
                            }  
                            ctx.lineTo( qx > cx ? qx : cx, qy < cy ? qy : cy);  
                            ctx.closePath();  
                            stars[j].nx = sx > cx ? sx : cx;  
                            stars[j].ny = sy < cy ? sy : cy;  
                        }  
                        if(dx > 0 && dy < 0)  
                        {  
                            var qx = x - ( xLife < xLast ? xLife : xLast);  
                            var qy = y - ( yLife > yLast ? yLife : yLast);  
                            ctx.moveTo( qx < cx ? qx : cx, qy > cy ? qy : cy);  
                            var sx = x - ( xCur > 0 ? xCur : 0);  
                            var sy = y - ( yCur < 0 ? yCur : 0);  
                            ctx.lineTo( sx < cx ? sx : cx, sy > cy ? sy : cy);  
                            if ( mod == "x" ){  
                                ctx.lineTo( qx < cx ? qx : cx, (qy > cy ? qy : cy) + 2);  
                            } else {  
                                ctx.lineTo( (qx < cx ? qx : cx) + 2, qy > cy ? qy : cy);  
                            }  
                            ctx.lineTo( qx < cx ? qx : cx, qy > cy ? qy : cy);  
                            ctx.closePath();  
                            stars[j].nx = sx < cx ? sx : cx;  
                            stars[j].ny = sy > cy ? sy : cy;  
                        }  


В зависимости от того, в какую четверть попадает наша звезда, знаки перед значениями и сравнения отличаются, поэтом код практически дублируется 4 раза. Кроме того в переменной mod запоминается в какую координату считать ведущей ( то есть приравнивать dx к currentLife или dy). Без mod-а звезды в близи оси ординат будут «пролетать» слишком быстро, из за большого угла.

И последнее замечание – в оригинале используется всего два цвета, поэтому за один проход отрисовка на Canvas происходит всего два раза (так как указано два цвета). Все звезды одного цвета формируются в один путь, после чего выводятся на Canvas.

Остается все это обернуть в цикл и запустить.

Подход 3. Правильный.

Под правильным подходом я понимаю использование распространенных готовых решений и библиотек. Готовых решений при беглом осмотре не нашлось. В качестве библиотеки я решил попробовать libcanvas. Благо на Хабрахабре он представлен достаточно сильно.

Полный код и демонстрация на jsfiddle. (JSFiddle может не подгрузить atom и libcanvas с github-а, так что возможно надо будет несколько раз перезагрузить страницу)

В итоге получилось следующее:

    new function () {  
        var center, i, helper, stars;  
  
        LibCanvas.extract();  
    
        helper = new App.Light(new Size( document.width, document.height));  
    
        center = helper.app.rectangle.center;  
    
        stars = [];  
        for(i = 0; i < 350; i++){  
            new function() {  
                var point = new Point(getRandomInt(document.width/2,document.width),document.height/2),  
                    length = getRandomInt(50,150),  
                    angle = getRandomInt(0,360),  
    
                    coords = [  
                        new Point(0,0),  
                        new Point(0,0),  
                        new Point(0,0)  
                    ],  
                    path = helper.createVector( new Path()  
                            .moveTo( coords[0] )  
                            .lineTo( coords[1] )  
                            .lineTo( coords[2] )  
                            .lineTo( coords[0] )).setStyle({fill:"rgb(150,150,150)",stroke:"rgb(150,150,150)"});  
    
                point.rotate( - angle.degree(), center);  
    
                var star = {  
                    point : point,  
                    length : length,  
                    angle : angle,  
                    coords : coords,  
                    live : 0,  
                    setLength : function(){  
                        if (arguments.length > 0){  
                            this.live = arguments[0];  
                        }  
                        this.coords[0].x = this.point.x;  
                        this.coords[0].y = this.point.y;  
                        this.coords[1].x = this.coords[0].x + this.live * Math.cos( this.angle.degree() );  
                        this.coords[1].y = this.coords[0].y - this.live * Math.sin( this.angle.degree() );  
                        this.coords[2].x = this.coords[1].x + 2 * Math.sin( this.angle.degree() );  
                        this.coords[2].y = this.coords[1].y + 2 * Math.cos( this.angle.degree() );  
                    },  
                    path : path  
                };  
                star.setLength();  
  
                stars.push(star);  
            };  
        }  
    
        setInterval(function(){  
            for(var i = 0; i < 350; i++){  
                stars[i].setLength( stars[i].live + 1 );  
                stars[i].path.redraw();  
            }  
        },10);    
  
    };  


Надо сказать, что здесь, в отличие от боевого варианта, используется адекватная математика с тригонометрией, но я не стал дописывать до того же функционала. Скорость выполнения кода на libCanvas не сильно отличается от нативного метода, а кода в разы меньше и скорость разработки заметно выше. С самого начала я не стал использовать libCanvas по нескольким причинам: я ни разу не пользовался им до этого, я привык к чистому JavaScript и я боялся, что версия надстройка будет заметно медленней. Как оказалось боялся зря.


UPD Правильная реализация на LibCanvas от TheShock

Пример на jsfiddle. Особенно приятно смотрится в fullScreen.

Создание и настройка звезды выносится в отдельный класс. Для каждой звезды определяется скорость, цвет и время жизни.

atom.declare( 'Star', App.Element, {
    
    progress: 0,
    opacity: 1,
    
    configure: function () {
        var screenRectangle = this.layer.ctx.rectangle;
        
        this.animate = new atom.Animatable(this).animate;
        this.from  = screenRectangle.getRandomPoint();
        this.shape = new Polygon(this.from.clone(), this.from.clone(), this.from.clone());
        this.angle = -this.from.angleTo(screenRectangle.center);
        this.sin   = this.angle.sin();
        this.cos   = this.angle.cos();
        
        this.progressSpeed = Math.random() + 0.5;
        this.color = new atom.Color(128, 128, Number.random(128, 192)).toString();
        
        setTimeout(this.fadeOut.bind(this), Number.random(1000, 8000));
    },
    
    fadeOut: function () {
        this.animate({
            time: 2000,
            props: { opacity: 0 },
            onComplete: this.destroy
        });
    },
    
    onUpdate: function () {
        var sin = this.sin, cos = this.cos, p = this.shape.points;
        
        this.progress += this.progressSpeed;
        
        p[1].x = p[0].x + this.progress * cos;
        p[1].y = p[0].y - this.progress * sin;
        p[2].x = p[1].x + 2 * sin;
        p[2].y = p[1].y + 2 * cos;
        
        this.redraw();
    },
    
    renderTo: function (ctx) {
        ctx.save();
        if (this.opacity < 1) ctx.set({ globalAlpha: this.opacity });
        ctx.fill( this.shape, this.color );
        ctx.restore();
    }
    
});


Изначально звёзд создаётся меньше, а потом добавляются в каджом кадре:

new function () {
    var helper = new App.Light(
        new Size(document.width || 800, document.height || 800),
        { intersection: 'full', invoke: true }
    );
    
    for(i = 0; i < 200; i++) new Star(helper.layer);
    
    atom.frame.add(function () {
        new Star(helper.layer);
    });
    
};

// UPD end

На этом все и еще раз с днем космонавтики!

Ссылки:
Пример с картинкой на jsfiddle.
«Боевой» пример на jsfiddle.
AtomJS и libCanvas для третьего примера.
Третий пример на libCanvas на jsfiddle. (может не заработать сразу из-за особенностей работы jsfiddle и github)
Промо сайт, для которого и создавался эффект.
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 48
  • +3
    Ну какое это гиперпространство, это виденье Лукаса как будут выглядеть звезды на высоких скоростях, а на самом деле это совсем не правда ведь :-) Эффект Доплера никто еще не отменял.
    • +1
      Да, я прекрасно понимаю что в реальности все должно выглядеть по другому, но согласитесь эффект узнаваем и, за счет фильмов про звездные перелеты, ожидаем.
      • +4
        А как на самом деле это должно выглядеть?
        • +31
          Что за вопрос, любой школьник знает как на самом деле выглядит гиперпространство :)
          • +2
            www.youtube.com/watch?v=lD08CuUi_Ek
            где-то около 6:45 именно на эту тему
            • 0
              Во-первых, вы капельку ошиблись со временем. Во-вторых, ю-тюб позволяет давать ссылки на конкретное время в ролике. По ссылке ниже — кусок вашего видео про путешествие от Земли к Луне со скоростью, близкой к скорости света. www.youtube.com/watch?feature=player_detailpage&v=lD08CuUi_Ek#t=469s
          • 0
            Чем ближе к скорости света, тем меньше вы сможете увидеть по краям, а будет видно при взгляде по направлению движения, при увеличении скорости в направлении источника света, волна будет становиться короче и соответственно будет смещаться в фиолетовую сторону, по сути будет фиолетовое пятно по направлению движения, если наоборот двигаться от источника света, то смещаться будет в сторону красного цвета, длинна волны будет увеличиваться. В итоге летите, впереди фиолетовое пятно, сзади красное, по бокам чернота.
            • 0
              Время замедлится на скоростях близких к скорости света, поэтому человек вероятнее всего ничего не увидет, а записывающее устройство не успеет сработать :)
          • +11
            У меня почему-то дикие тормоза при просмотре примера на libCanvas. А так всё очень даже хорошо!
            • 0
              У меня с libcanvas заметно тормозит, еще и все серое. (Chromium 25.0.1364.160 Ubuntu 12.04)
              С pure JS все красиво и плавно работает.
              • 0
                В libCanvas это я поставил цвет звезд серый, когда писал — думала только на время, но в итоге забыл поставить обратно белый, сейчас исправлю.
                • 0
                  У меня нативный вариант все равно работает раз в 5 быстрее.
                  Опера 12.15
                  В Chrome видно разницу на глаз, но не так разительно как в Опера.
                  • 0
                    IE10 версия libCanvas не работает вообще.
                    • 0
                      Сценарий из raw.github.com/theshock/libcanvas/master/libcanvas-full-compiled.js был заблокирован из-за несоответствия типа MIME

                      Исправить очень легко — брать файлы с другого места:
                      jsfiddle.net/UM3vR/27/

                      Но это очередной фейл IE, имхо.
                      • +1
                        Ну, давайте теперь ругать IE за соблюдение стандарта Http :)
                        • 0
                          А разве в стандарте http расписан подход к блокировке JS-файлов?)
                          • 0
                            В стандарте описана реакция сервера и клиента на заголовок Accept.
                      • 0
                        Да, этот работает как минимум так же плавно как пример с картинкой. За счет эффекта угасания выглядит приятнее :)
                        Но в IE9/10 не работает -((
                • +1
                  Пророчу выходные различных гиперпространств на JS.
                  • +1
                    Странно, что точки резко останавливаются на определенном месте и ждут, когда хвост растворится. По идее, «голова» тоже должна плавно растворяться, не прекращая движения.
                    • +2
                      Сразу вспомнился скринсейвер звезды в windows 3.1 и 9.x
                      • +1
                        Да, тоже про нее сразу вспомнил, когда мне только рассказали идею.
                      • +2
                        Гиперпространство это когда мы перемещаемся не с большой скоростью, а когда мы перемещаемся в другом пространстве. А у Лукаса и у вас получилась просто скорость света типа. Вот в Вавилоне 5 было гиперпространство.
                        • +6
                          Я примерно такого же эффекта добивался вот в этой визуализации (только визуализация без версии).
                          image
                          Тут только не звезды а денежные займы от одной стране к другой. Библиотека d3.js для расчета физики.
                          • +1
                            Отличная идея и реализация.
                            • +3
                              Почему у вас африканские займы идут куда-то в Гренландию? Я сначала прифигел, как страна денег гребет :)
                              • +1
                                По каким то причинам у некоторых стран в БД world bank корректных координат или допустим данные по всему региону… поэтому я не могу привязать правильно координаты столицы странны и вывожу подобные страны вверху, там где ничего нет.
                            • 0
                              Было бы круто добавить интерактива — новые вычисления запускать относительно последнего положения курсора ( или по вектору).
                              • +15
                                Как-то так? jsfiddle.net/agegorin/gdK8Z/
                                • +1
                                  круто) я аж машинально приподнял мышку над столом в попытках приблизить к себе центр отсчета по оси Z ))
                                  • +1
                                    Это уже эффект того, что вы чешите ёжика палочкой…
                                    • 0
                                      Я прям захотел этого ежа погладить =)
                                  • 0
                                    Эх, как будто космонавтов больше нет, одни астронавты… У нас в городе даже как-то раз повесили рекламу на английском, вместо шоссе Космонавтов написали Astronauts avenue.
                                    • 0
                                      Ну, на том сайте и космические достижения упомянуты в основном лишь американские.

                                      А если по теме, то мне понравилась реализация перехода в гиперпространство.
                                    • +6
                                      Позвольте внести правки в ваш код на LibCanvas

                                      helper = new App.Light(new Size( width, height ));
                                      // =>
                                      helper = new App.Light(new Size( width, height), { intersection: 'full' });
                                      


                                      результат — значительное повышение скорости.

                                      Прикол в том, что по-умолчанию библиотека считает «Dirty Rectangles», что в даном случае, очевидно, не очень выгодный подход в плане производительности.

                                      Ну и несколько мелочей — вместо Path можно использовать Polygon
                                      Вместо getRandomInt использовать встроенную Number.random
                                      Вместо setInterval atom.frame.add

                                      jsfiddle.net/UM3vR/15/

                                      Жаль, что функциональность не совсем та, что в оригинальных примерах, а в целом — прикольно)
                                      • +1
                                        Вот именно этого комментария я и ждал. =)
                                        Так и знал что я что-то упустил при написании кода.
                                        Спасибо, буду дальше углубляться в LibCanvas и Atom.
                                        • +1
                                          Ну тогда я бы сделал это так:

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

                                          atom.declare( 'Star', App.Element, {
                                              
                                              progress: 0,
                                              opacity: 1,
                                              
                                              configure: function () {
                                                  var screenRectangle = this.layer.ctx.rectangle;
                                                  
                                                  this.animate = new atom.Animatable(this).animate;
                                                  this.from  = screenRectangle.getRandomPoint();
                                                  this.shape = new Polygon(this.from.clone(), this.from.clone(), this.from.clone());
                                                  this.angle = -this.from.angleTo(screenRectangle.center);
                                                  this.sin   = this.angle.sin();
                                                  this.cos   = this.angle.cos();
                                                  
                                                  this.progressSpeed = Math.random() + 0.5;
                                                  this.color = new atom.Color(128, 128, Number.random(128, 192)).toString();
                                                  
                                                  setTimeout(this.fadeOut.bind(this), Number.random(1000, 8000));
                                              },
                                              
                                              fadeOut: function () {
                                                  this.animate({
                                                      time: 2000,
                                                      props: { opacity: 0 },
                                                      onComplete: this.destroy
                                                  });
                                              },
                                              
                                              onUpdate: function () {
                                                  var sin = this.sin, cos = this.cos, p = this.shape.points;
                                                  
                                                  this.progress += this.progressSpeed;
                                                  
                                                  p[1].x = p[0].x + this.progress * cos;
                                                  p[1].y = p[0].y - this.progress * sin;
                                                  p[2].x = p[1].x + 2 * sin;
                                                  p[2].y = p[1].y + 2 * cos;
                                                  
                                                  this.redraw();
                                              },
                                              
                                              renderTo: function (ctx) {
                                                  ctx.save();
                                                  if (this.opacity < 1) ctx.set({ globalAlpha: this.opacity });
                                                  ctx.fill( this.shape, this.color );
                                                  ctx.restore();
                                              }
                                              
                                          });
                                          


                                          Изначально звёзд создать поменьше, а потом добавлять в каджом кадре
                                          new function () {
                                              var helper = new App.Light(
                                                  new Size(document.width || 800, document.height || 800),
                                                  { intersection: 'full', invoke: true }
                                              );
                                              
                                              for(i = 0; i < 200; i++) new Star(helper.layer);
                                              
                                              atom.frame.add(function () {
                                                  new Star(helper.layer);
                                              });
                                              
                                          };
                                          


                                          Код стал лаконичнее и читабельнее. В браузере выглядит красиво и работает быстро. Особенно приятно смотрится fullScreen


                                          Добавите в топик обновлённый код?
                                          Не против, если я добавлю этот пример в репозиторий примеров на ГитХабе?
                                          • +1
                                            Да, сейчас добавлю Ваш вариант в статью.
                                            И нет, я не против того, что бы пример был в репозитории. Буду даже польщен =)
                                            • 0
                                              Я на самом деле забыл добавить зависимости от времени:
                                              Например, теперь при открытии другой вкладки и возвращении не будет пустое поле. Прикол в том, что браузеры замедляют обновление до «раз в секунду» в неактивных вкладках, потому это стоит отдельно просчитывать.
                                        • +2
                                          Час ждал, но обломки Алдерана так и не появились. FireFox 20.0.1, прошу пофиксить
                                          • +1
                                            После вашей демки захотелось космический симулятор вроде Descent: freespace, но в браузере.
                                            • +3
                                              когда вижу словосочетание canvas + анимация, вместе с примером наслаждаюсь графиком CPU
                                              • 0
                                                Нашел у знакомого в Твиттере: slonique.net
                                                • 0
                                                  Сильно быстро и слоник — лишний)
                                                  • 0
                                                    Слоник не лишний.
                                                    Гауптпространство же.
                                                • +2
                                                  Может кому-то пригодится: реализация варп-прыжка

                                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.