html текст
All interests
  • All interests
  • Design
  • Food
  • Gadgets
  • Humor
  • News
  • Photo
  • Travel
  • Video
Click to see the next recommended page
Like it
Don't like
Add to Favorites

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

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



В одном проекте, приуроченном к сегодняшнему празднику дизайнерами была поставлена задача создать имитацию гиперпространства. Немного поразмыслив решил что правильнее будет использовать 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)
Промо сайт, для которого и создавался эффект.
Читать дальше
Twitter
Одноклассники
Мой Мир

материал с habrahabr.ru

1

      Add

      You can create thematic collections and keep, for instance, all recipes in one place so you will never lose them.

      No images found
      Previous Next 0 / 0
      500
      • Advertisement
      • Animals
      • Architecture
      • Art
      • Auto
      • Aviation
      • Books
      • Cartoons
      • Celebrities
      • Children
      • Culture
      • Design
      • Economics
      • Education
      • Entertainment
      • Fashion
      • Fitness
      • Food
      • Gadgets
      • Games
      • Health
      • History
      • Hobby
      • Humor
      • Interior
      • Moto
      • Movies
      • Music
      • Nature
      • News
      • Photo
      • Pictures
      • Politics
      • Psychology
      • Science
      • Society
      • Sport
      • Technology
      • Travel
      • Video
      • Weapons
      • Web
      • Work
        Submit
        Valid formats are JPG, PNG, GIF.
        Not more than 5 Мb, please.
        30
        surfingbird.ru/site/
        RSS format guidelines
        500
        • Advertisement
        • Animals
        • Architecture
        • Art
        • Auto
        • Aviation
        • Books
        • Cartoons
        • Celebrities
        • Children
        • Culture
        • Design
        • Economics
        • Education
        • Entertainment
        • Fashion
        • Fitness
        • Food
        • Gadgets
        • Games
        • Health
        • History
        • Hobby
        • Humor
        • Interior
        • Moto
        • Movies
        • Music
        • Nature
        • News
        • Photo
        • Pictures
        • Politics
        • Psychology
        • Science
        • Society
        • Sport
        • Technology
        • Travel
        • Video
        • Weapons
        • Web
        • Work

          Submit

          Thank you! Wait for moderation.

          Тебе это не нравится?

          You can block the domain, tag, user or channel, and we'll stop recommend it to you. You can always unblock them in your settings.

          • habrahabr.ru
          • домен habrahabr.ru

          Get a link

          Спасибо, твоя жалоба принята.

          Log on to Surfingbird

          Recover
          Sign up

          or

          Welcome to Surfingbird.com!

          You'll find thousands of interesting pages, photos, and videos inside.
          Join!

          • Personal
            recommendations

          • Stash
            interesting and useful stuff

          • Anywhere,
            anytime

          Do we already know you? Login or restore the password.

          Close

          Add to collection

             

            Facebook

            Ваш профиль на рассмотрении, обновите страницу через несколько секунд

            Facebook

            К сожалению, вы не попадаете под условия акции