HTML5 Canvas wydajność i optymalizacja Porady, triki i najlepsze praktyki kodowania [zamknięty]

CZY ZNASZ WIĘCEJ NAJLEPSZYCH PRAKTYK DLA CANVAS??

Dodaj do tego wątku to, co wiesz, czego się nauczyłeś lub przeczytałeś online wszelkie najlepsze praktyki Canvas, porady / wskazówki dotyczące wydajności

Ponieważ Canvas jest wciąż bardzo nowy w Internecie i nie ma oznak, że się starzeje, które mogę zobaczyć w przyszłości, nie ma zbyt wielu udokumentowanych "najlepszych praktyk" lub innych naprawdę ważnych wskazówek, które są "Must know" do rozwijania z nim w jednym konkretnym miejscu. Rzeczy jak to są rozrzucone wokół i wiele razy na mniej znanych miejscach.

Jest tak wiele rzeczy, o których ludzie muszą wiedzieć, i wciąż jest tak wiele do nauczenia się o zbyt.


Chciałem podzielić się kilkoma rzeczami, aby pomóc ludziom, którzy uczą się Canvas, A może niektórym, którzy już go znają dość dobrze i mam nadzieję uzyskać opinie od innych o tym, co uważają za najlepsze praktyki lub inne wskazówki i wskazówki dotyczące pracy z Canvas w HTML5.

Chcę zacząć off z jednym, który osobiście uważam za dość przydatną, ale zaskakująco rzadką rzecz dla programistów.

1. Wpisz swój kod

Tak jak w każdym innym czasie, w każdym innym języku, cokolwiek by to nie było. To była najlepsza praktyka dla wszystkiego innego, i doszedłem do wniosku, że w złożonej aplikacji canvas, rzeczy mogą być nieco mylące, gdy mamy do czynienia z kilkoma różnymi kontekstami i zapisanymi/przywracanymi Stanami. Nie wspominając o tym, że kod jest po prostu bardziej czytelny i ogólny Sprzątaczka też.

Na przykład:

...
// Try to tell me this doesn't make sense to do
ctx.fillStyle = 'red';
ctx.fill();
ctx.save();
    if (thing < 3) {
        // indenting
        ctx.beginPath();
            ctx.arc(2, 6, 11, 0, Math.PI*2, true);
        ctx.closePath();
        ctx.beginPath();
            ctx.moveTo(20, 40);
            ctx.lineTo(10, 200);
            ctx.moveTo(20, 40);
            ctx.lineTo(100, 40);
        ctx.closePath();
        ctx.save();
            ctx.fillStyle = 'blue'
            ctx.fill();
        ctx.restore();
    } else { 
        // no indenting
        ctx.drawImage(img, 0, 0, 200, 200);
        ctx.save();
        ctx.shadowBlur();
        ctx.beginPath();
        ctx.arc(2, 60, 10, 0, Math.PI*2, false);
        ctx.closePath();
        ctx.fillStyle 'green';
        ctx.fill();
        ctx.restore();
    }
ctx.restore();
ctx.drawRect();
ctx.fill();
...

Czy stwierdzenie IF nie jest łatwiejsze i czystsze do odczytania i poznania, co jest tym, co się natychmiast dzieje, niż stwierdzenie ELSE w tym? Widzisz, o czym mówię? Myślę, że powinna to być metoda, że programiści powinni nadal praktykować tak, jak przy pisaniu zwykłego javascript ol lub jakiegokolwiek innego języka nawet.

Użyj requestAnimationFrame zamiast setInterval / setTimeout

SetInterval i setTimeout były nigdy nie były przeznaczone do użycia jako timery animacji, są to tylko ogólne metody wywoływania funkcji po opóźnieniu czasowym. Jeśli w przyszłości ustawisz interwał na 20 ms, ale wykonanie kolejki funkcji trwa dłużej, zegar nie zostanie uruchomiony, dopóki te funkcje nie zostaną zakończone. To może trochę potrwać, co nie jest idealne w przypadku animacji. RequestAnimationFrame jest metodą, która informuje przeglądarkę, że odbywa się animacja, dzięki czemu może zoptymalizować odpowiednio przemalowuje. Dławi również animację nieaktywnych kart, więc nie zabije baterii urządzenia mobilnego, jeśli zostawisz ją otwartą w tle.

Nicholas Zakas napisał na swoim blogu niezwykle szczegółowy i pouczający artykuł o requestAnimationFrame , który warto przeczytać. Jeśli potrzebujesz twardych i szybkich instrukcji implementacji, to Paul Irish napisał requestAnimationFrame shim – tego użyłem w każdym z Canvas aplikacje, które robiłem do niedawna.

Właściwie

Nawet lepiej niż używanie requestAnimationFrame zamiast setTimeout i setInterval, Joe Lambert napisał nowy i Ulepszony shim o nazwie requestInterval i requestTimeout, który wyjaśnia, jakie problemy występują podczas używania requestAnimFrame. Możesz wyświetlić gist skryptu.

Właściwie x2

Teraz, gdy wszystkie przeglądarki dogoniły specyfikację, nastąpiła aktualizacja do requestAnimFrame () polyfill, który prawdopodobnie pozostanie tym, którego można użyć do pokrycia wszystkich dostawców.

Użyj więcej niż jednego płótna

Technika dla animacji ciężkich gier, które@nicolahibbert pisała o wpost jej na optymalizacji gier Płótno wspomina, że może być lepiej używać wielu płótna warstwowych jeden na drugim, a nie robić wszystko w jednym płótnie. Nicola wyjaśnia, że " rysowanie zbyt wielu pikseli na tym samym płótnie w w tym samym czasie liczba klatek na sekundę spadnie przez podłogę. Weźmy na przykład Breakout. Próbując narysować Cegły, piłkę, wiosło, wszelkie power-upy lub broni, a następnie każda gwiazda w tle – to po prostu nie zadziała, to trwa zbyt długo, aby wykonać każdą z tych instrukcji po kolei. Dzieląc pole gwiezdne i resztę gry na oddzielne płótna, możesz zapewnić przyzwoitą ilość klatek na sekundę."

Renderowanie elementów poza ekranem

Musiałem to zrobić od kilku aplikacje zrobiłem w tym Olympic Genome Project Samsung facebook app . To niezwykle przydatna rzecz wiedzieć i wykorzystać to, czy jest to potrzebne, czy nie. Znacznie skraca czas ładowania, a także może być bardzo przydatną techniką ładowania obrazów poza ekranem, ponieważ czasami mogą one zająć trochę czasu.

var tmpCanvas = document.createElement('canvas'),
    tmpCtx = tmpCanvas.getContext('2d'),
    img = document.createElement('img');

img.onload = function() {
    tmpCtx.drawImage(thumbImg, 0, 0, 200, 200);
};
img.src = '/some/image/source.png';

Zauważ, że src obrazu jest ustawiany po jego załadowaniu. To jest kluczowa rzecz, o której należy pamiętać. Po załadowaniu obrazów i pobraniu ich do tych temp płótna można następnie narysować na głównym płótnie za pomocą tego samego ctx.drawImage (), ale zamiast stawiać obraz jako pierwszy argument, używasz 'tmpCtx.canvas", aby odwołać się do temp canvas.

Inne porady, triki i zasoby

Canvas ma back-reference

Kontekst 2d ma odwołanie do powiązanego z nim elementu DOM:

var ctx = doc.getElementById('canvas').getContext('2d');
console.log(ctx.canvas);    // HTMLCanvasElement
Chciałbym usłyszeć więcej od innych ludzi na ten temat. Pracuję nad sporządzeniem listy rzeczy, które powinniśmy ujednolicić, aby dodać nową sekcję do standardów kodu Front-end mojej firmy i Najlepszych Praktyk. Chciałbym uzyskać jak najwięcej opinii na ten temat, Jak mogę.
Author: jaredwilli, 2011-11-21

4 answers

Przerysuj Regiony

Najlepszą techniką optymalizacji płótna dla animacji jest ograniczenie liczby pikseli, które są czyszczone/malowane na każdej klatce. Najprostszym rozwiązaniem do wdrożenia jest zresetowanie całego elementu canvas i rysowanie wszystkiego od nowa, ale jest to kosztowna operacja dla przeglądarki do przetworzenia.

Użyj ponownie jak największej liczby pikseli pomiędzy klatkami. Oznacza to, że im mniej pikseli trzeba przetworzyć każdą klatkę, tym szybciej program będzie działał. Na przykład podczas kasowania pikseli za pomocą metody clearRect(x, y, w, h) bardzo korzystne jest czyszczenie i przerysowywanie tylko tych pikseli, które się zmieniły, a nie pełnego obszaru roboczego.

Proceduralne Sprity

Proceduralne generowanie grafiki jest często drogą do zrobienia, ale czasami nie jest to najbardziej efektywne. Jeśli rysujesz proste kształty z wypełnieniami stałymi, najlepszym sposobem jest ich proceduralne rysowanie. Ale jeśli rysujesz bardziej szczegółowe elementy za pomocą obrysów, gradient wypełnia i inne makijaże wrażliwe na wydajność, lepiej byłoby użyć sprite ' ów obrazu.

Jest możliwe, aby uciec z mieszanką obu. Rysuj elementy graficzne proceduralnie na kanwie po uruchomieniu aplikacji. Następnie możesz ponownie użyć tych samych sprite ' ów, malując ich kopie zamiast generować ten sam cień, gradient i obrysy wielokrotnie.

State Stack & Transformation

Płótno można manipulować za pomocą przekształceń, takich jak obrót i skalowanie, co skutkuje zmianą układu współrzędnych obszaru roboczego. W tym miejscu ważne jest, aby wiedzieć o stosie stanu, dla którego dostępne są dwie metody: context. save () (wypycha bieżący stan na stos) i context.restore () (powraca do poprzedniego stanu). Dzięki temu można zastosować transformację do rysunku, a następnie przywrócić poprzedni stan, aby upewnić się, że wcześniejsza transformacja nie wpływa na następny kształt. Stany obejmują również takie właściwości jak wypełnienie i kolory obrysu.

Compositing

Bardzo potężnym narzędziem w zasięgu ręki podczas pracy z płótnem są tryby komponowania, które między innymi pozwalają na maskowanie i układanie warstw. Istnieje szeroki wachlarz dostępnych trybów kompozytowych i wszystkie są ustawiane za pomocą właściwości globalCompositeOperation kontekstu canvas. Tryby kompozytowe są również częścią właściwości stosu stanu, więc można zastosować operację kompozytową, ustawić stan w stosie i zastosować inny, a także przywrócić do stanu przed tym, gdzie zrobiłeś pierwszy. Może to być szczególnie przydatne.

Antyaliasing

Aby umożliwić rysowanie subpikseli, wszystkie implementacje przeglądarki canvas wykorzystują antyaliasing (chociaż nie wydaje się to być wymogiem w specyfikacji HTML5). Wygładzanie może być ważne, aby pamiętać, jeśli chcesz narysować wyraźne linie i zauważyć, że wynik wygląda na niewyraźny. Dzieje się tak, ponieważ przeglądarka interpoluje obraz tak, jakby znajdował się między tymi pikselami. Wyniki w znacznie płynniejszej animacji (możesz naprawdę poruszać się o pół piksela na aktualizację), ale sprawi, że Twoje obrazy będą wydawać się rozmyte.

Aby to obejść, musisz albo zaokrąglić do całych wartości całkowitych, albo przesunąć o pół piksela w zależności od tego, czy rysujesz wypełnienia lub obrysy.

Używanie liczb całkowitych dla pozycji drawImage() X i Y

Jeśli wywołasz drawImage na elemencie Canvas, znacznie szybciej będzie zaokrąglenie pozycji x i y do liczby całkowitej.

Oto przykład testowy na jsperf pokazujący, o ile szybsze jest używanie liczb całkowitych w porównaniu z używaniem dziesiętnych.

Więc zaokrągl swoją pozycję x i y do liczb całkowitych przed renderowaniem.

Szybciej niż matematyka.round ()

Kolejny test jsperf pokazuje , że matematyka.round() niekoniecznie jest najszybszą metodą zaokrąglania liczb. Korzystanie bitowe hack faktycznie okazuje się być szybsze niż wbudowany w metodzie.

Canvas Sprite Optymalizacja

Czyszczenie płótna

Aby wyczyścić cały obszar roboczy z dowolnego istniejącego kontekstu pikseli.clearRect (x, y, W, h) jest zazwyczaj używany – ale jest inna opcja dostępna. Za każdym razem, gdy szerokość/wysokość obszaru roboczego jest ustawiona, nawet jeśli są ustawiane wielokrotnie na tę samą wartość, obszar roboczy jest resetowany. Warto o tym wiedzieć podczas pracy z dynamicznym płótnem, ponieważ zauważysz znikanie rysunków.

Rozkład Obliczeń

Programista Chrome Tools profiler jest bardzo przydatny, aby dowiedzieć się, jakie są wąskie gardła wydajności. W zależności od aplikacji, konieczne może być odświeżanie niektórych części programu, aby poprawić wydajność i sposób, w jaki przeglądarki obsługują określone części kodu.

Techniki optymalizacji

 15
Author: jaredwilli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-13 07:56:56

Oto moje wskazówki

1) Użyj clearRect, aby wyczyścić płótno zamiast płótna./ width = " 50%" / szerokość, ponieważ później resetuje Stany canvas

2) Jeśli używasz zdarzeń myszy na kanwie użyj następującej funkcji, jej jest niezawodna i działa w większości przypadków.

/**  returns the xy point where the mouse event was occured. 
 @param ev The event object.
*/
function getXY(ev){
   return getMousePosition(ev, ev.srcElement || ev.originalTarget);
}

 /**  returns the top-left point of the element
       @param elem The element
   */
function getElementPos(elem){
   var obj = elem;
   var top = 0;
   var left = 0;
    while (obj && obj.tagName != "BODY") {
      top += obj.offsetTop-obj.scrollTop;
      left += obj.offsetLeft -obj.scrollLeft ;
      obj = obj.offsetParent;
     }
  return {
    top: top,
    left: left
    };
};

/**  returns the xy point where the mouse event was occured inside an element. 
@param ev The event object.
 @param elem The element
*/
function getMousePosition(evt, elem){
var pageX, pageY;
if(typeof(window.pageYOffset)=='number') {
    pageX=window.pageXOffset;
    pageY=window.pageYOffset;
}else{
    pageX=document.documentElement.scrollLeft;
    pageY=document.documentElement.scrollTop;
}
var mouseX = evt.clientX - getElementPos(elem).left + pageX;
var mouseY = evt.clientY - getElementPos(elem).top + pageY;
return {
    x: mouseX,
    y: mouseY
};
};

3) Użyj ExplorerCanvas, jeśli chcesz wspierać IE7

4) zamiast wyczyścić całe płótno Wyczyść tylko część, która jest potrzebna do czyszczenia. To dobre dla wydajności.

 2
Author: Shamaila Tahir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-10 07:35:32

Facebook Facebook, który używa Canvas i użytkowników informacje profilowe (ilość danych musi pomieścić jest ogromna dla niektórych), aby dopasować Ciebie i twoich znajomych również za pomocą aplikacji, do sportowców olimpijskich jak 6 stopni typu separacji rzeczy, jest sporo nauczyłem się w moich rozległych wysiłków, aby zrobić wszystko, co mogłem ewentualnie spróbować zwiększenia wydajności w aplikacji.

Dosłownie spędziłem miesiące i dni w czas na ponowne uwzględnienie kodu, który znałam już tak dobrze i uważałam, że jest to najbardziej optymalny sposób na robienie rzeczy.

Użyj elementów DOM W miarę możliwości

Faktem jest, że przeglądarki nadal nie są gotowe do obsługi bardziej intensywnych uruchomionych aplikacji w Canvas, zwłaszcza jeśli jesteś zobowiązany do opracowania aplikacji z obsługą IE 8. Czasami zdarza się, że DOM jest szybszy niż obecna implementacja Canvas API w momencie pisania tego tekstu. At przynajmniej znalazłem go podczas pracy nad niezwykle złożoną pojedynczą stroną animującą aplikację html5 i canvas dla Samsunga.

Byliśmy w stanie zrobić całkiem dobrze w poprawie wydajności rzeczy, nadal używając Canvas do wykonywania skomplikowanej pracy, aby przyciąć obrazy do kręgów, co prawdopodobnie byłoby w porządku trzymać się tego, jak to robimy.

Na Kilka dni przed premierą postanowiliśmy spróbować innej techniki, a nie tworzyć tymczasowe płótna poza ekranem, które były umieszczone na widocznym płótnie po przycięciu do kółek itp.., właśnie dodaliśmy Elementy Dom obrazu na płótnie, używając współrzędnych x i y, których wcześniej używaliśmy do umieszczania płócien temp.

Do przycinania obrazów w okręgi, cóż, to było proste, po prostu użyliśmy właściwości CSS3 border-radius, aby to zrobić, co było znacznie mniej pracy niż złożona seria zmian stanu i podczas genialne i twórcze, ale nadmierne wykorzystanie .metoda clip ().

Po umieszczeniu w DOM występuje animacja obrazów, a węzły DOM dla każdego obrazu są animowane jako osobne elementy obszaru roboczego. Te, które możemy mieć pełną kontrolę nad stylizacją off łatwo poprzez CSS.

Technika ta jest podobna do innej metody wykonywania tego typu prac, którą warto znać, która polega na nakładaniu płócien na siebie, a nie rysowaniu ich w jednym kontekście.

 2
Author: jaredwilli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-14 23:02:01

Oto kilka wskazówek i sugestii, które wczoraj umieściłem na liście, którą warto się podzielić.

  • Nie dołączaj jQuery, chyba że musisz zrobić coś więcej niż tylko wybrać <canvas>.

    Udało mi się obejść bez niego prawie wszystko, co zrobiłem na płótnie.]}
  • Tworzenie abstrakcyjnych funkcji i oddzielić kod . Oddziel funkcjonalność od wyglądu lub początkowego stanu rysowania.

    Spraw, aby wspólne funkcje były wielokrotnego użytku jako jak najbardziej. Najlepiej użyć wzorca modułu, w którym można utworzyć obiekt utils zawierający wspólne funkcje.

  • Używaj jedno-i dwuliterowych nazw zmiennych, gdy ma to sens (x, y, z ).

    Układ współrzędnych w kanwie dodaje więcej pojedynczych liter, które są powszechnie deklarowane jako zmienne. Co może prowadzić do powstania wielu zmienne pojedyncze/podwójne (dX, dY, aX, aY, vX, vY ) jako część elementu.

    Proponuję piszesz albo abbr. słowo ( dirX, accelX, velX) lub być opisowe, w przeciwnym razie rzeczy mogą być dość mylące dla ciebie później, zaufaj mi.

  • Utwórz funkcje konstruktora, które mogą być wywoływane w razie potrzeby do tworzenia elementów gry. Możesz dodać własne metody i właściwości w konstruktorze i utworzyć dowolną liczbę potrzebnych Ci metod, a wszystkie będą miały swoje własne właściwości i metody.

    Przykład funkcji konstruktora kuli I wykonane:

    // Ball constructor
    var Ball = function(x, y) {
        this.x = x;
        this.y = y;
    
        this.radius = 10;
        this.color = '#fff';
    
        // Direction and min, max x,y
        this.dX = 15;
        this.dY = -15;
    
        this.minX = this.minY = 20 + this.radius;
        this.maxX = this.radius - (canvasWidth - 20);
        this.maxY = this.radius + canvasHeight;
    
        this.draw = function(ctx) {
            ctx.beginPath();
                ctx.arc(this.x, this.y, this.radius, 0, twoPI, true);
            ctx.closePath();
            ctx.save();
                ctx.fillStyle = this.color;
                ctx.fill();
            ctx.restore();
        };
    };
    

Tworzenie piłki

ball = new Ball(centerX, canvasHeight - paddle.height - 30);
ball.draw(ctx);
  • Dobrą bazą do pracy jest stworzenie 3 funkcji: init () - wykonuje całą początkową pracę i ustawia podstawowe var-y i programy obsługi zdarzeń itp... draw () - wywoływane jednorazowo, aby rozpocząć grę i rysuje pierwszą klatkę gry, w tym tworzenie elementów, które mogą się zmieniać lub wymagać zbudowania. update() - wywołana na końcu Draw () i wewnątrz siebie poprzez requestAnimFrame. Aktualizuje właściwości zmieniając elementy, Rób tylko to, co musisz zrobić tutaj.

  • Wykonaj jak najmniej pracy w pętli, dokonując aktualizacji zmieniających się części lub elementów. Tworzenie elementów gry wykonaj inne działania interfejsu poza pętlą animacji.

    Pętla animacji jest często funkcją rekurencyjną, co oznacza, że wywołuje się szybko i wielokrotnie podczas animacji, aby narysować każdą klatkę.

    Jeśli jest wiele elementów animowanych jednocześnie, możesz najpierw utworzyć elementy używające funkcji konstruktora, jeśli jeszcze nie, a następnie w konstruktorze tworzą metodę 'timer', która ma requestAnimFrame / setTimeout używając go tak, jak normalnie w dowolnej pętli animacji, ale powoduje tylko ten element.

    Każdy element gry może mieć własne metody timer, draw i animate w konstruktorze.

    Daje to pełną separację sterowania dla każdego elementu i jedna duża pętla animacji nie będzie konieczne w ogóle, ponieważ pętla jest podzielona na każdy element i uruchamiasz / zatrzymujesz się do woli.

Lub inna opcja:

  • Utwórz funkcję konstruktora Timer () , której możesz użyć i nadać każdy element animacji indywidualnie, minimalizując w ten sposób obciążenie pracy w pętlach animacji
 1
Author: jaredwilli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-30 13:10:21