Perfection or Vanity

Project: Terminated

Blog nie jest już dalej prowadzony ani aktualizowany. Mimo tego, wpisy i komentarze są dalej dostępne. Możesz przeczytać pożegnalny wpis albo przejść do archiwum.

Zaletą rozdzielenia projektowania na warstwy zawartości, prezentacj i zachowania jest skrócenie czasu ładowania się strony. Linkujemy w head pliki CSS i JavaScript, a te przechowywane w cache nie ładują się za każdym razem spowalniając połączenie. W przypadku skryptów JS istnieje jednak dość poważny mankament, który powstrzymuje wielu webdeveloperów przed stosowaniem zewnętrznych skryptów.

Czas wykonania. Linkując skrypty z head mamy dostęp do elementów dopiero gdy cała strona się załaduje, po zdarzeniu window.onload. Gdy zaprojektowaliśmy stronę bogatą w multimedialną zawartość (choćby same obrazki) to minie dłuższa chwila zanim nasze skrypty zostaną przywołane do życia.

Znakomicie widać to na przykładzie strony komentarzy na Wykop.pl — serwer łączy się z odległym Gravatarem ściągając po kolei obrazki użytkowników. Wystarczy, że będzie działo się to wolniej niż zwykle albo połączenie zostanie zakłócone i nie możemy odpowiadać na komentarze użytkowników (inna sprawa, że ta funkcja powinna być nieinwazyjna).

Od niedawna istnieje wydajny sposób na przyspieszenie wykonywania skryptów na naszych stronach, wymyślony przez hakerów JS (Dean Edwards, Matthias Miller, John Resig). Mimo, że wspominałem już o szybszym odpalaniu skryptów, to w międzyczasie pojawiło się kilka usprawnień i haków, które determinują szerokie użycie zdarzenia załadowania drzewa dokumentu DOM – bo to właśnie ratuje nasze skóry przed czekaniem na wszelkie binarne plki.

Firefox, Opera

Najprościej uruchomić skrypty po załadowaniu się DOM w Firefoksie (Mozilli) i Operze (od wersji 9) – wspierają one niestandardowe zdarzenie DOMContentLoaded. Użycie zwyczajne, przez addEventListener:

  1. if (document.addEventListener) {
  2. document.addEventListener("DOMContentLoaded", callback, false);
  3. }

Drugi parametr callback to nazwa uruchamianej funkcji.

Internet Explorer

W przeglądarce Microsoftu dostępny jest dodatkowy atrybut dla elementu script - defer. Sygnalizuje on IE, że ma poczekać z wykonaniem skryptów aż do momentu załadowania się DOM.

Najlepiej w tym miejscu wykonać małą detekcję Explorera przez komentarze warunkowe:

  1. var browser = {};
  2. browser.explorer = /*@cc_on!@*/false;

Dodajemy przez szatański document.write (w innym przypadku nie zadziała) tag.

  1. if (browser.explorer) {
  2. document.write('<script id="_defer" defer="true" src="//:"><\/script>');
  3. }

I czekamy aż zostanie załadowany i uruchomiony.

  1. if (browser.explorer) {
  2. var deferScript = document.getElementById('_defer');
  3. if (deferScript) {
  4. deferScript.onreadystatechange = function() {
  5. if (this.readyState == 'complete') {
  6. callback();
  7. }
  8. };
  9. deferScript.onreadystatechange();
  10. deferScript = null;
  11. }
  12. }

Safari, Konqueror

Dzięki twórcy jQuery możliwe jest także dodanie DOMContentLoaded do przeglądarki Apple (i Konquerora). Potrzeba tutaj znowu wykonać sprawdzanie na czym operujemy (korzystając z lepszych metod niż sam ciąg UA):

  1. browser.webkit = /Apple|KDE/i.test(navigator.vendor);

Dodajemy timer, który sprawdza co 10 milisekund, czy wartość document.readyState zmieniła się na loaded albo complete - wtedy wiemy, że DOM został cały załadowany.

  1. if (browser.webkit) {
  2. var _timer = setInterval(function() {
  3. if (/loaded|complete/.test(document.readyState)) {
  4. clearInterval(_timer);
  5. callback();
  6. }
  7. }, 10);
  8. }

Inne przeglądarki

Pozostałe przeglądarki dostają zwyczajny window.onload.

Wywołanie funkcji

Aby zabezpieczyć się przed parukrotnym wywołaniem funkcji callback, należy dodać taki kod:

  1. function callback() {
  2. if (arguments.callee.done) { return; }
  3. arguments.callee.done = true;
  4. //twoje funkcje. np: roundCorners()
  5. }

Finalny kod

Dla przejrzystości możemy zebrać wszystkie funkcje w jedną, której przekazujemy callback (na przykład funkcję init() włączającą nasze skrypty).

Pobierz: document.ready.js.
Demo przedstawiające kod w działaniu.

Biblioteki

Dodam jeszcze tylko, że jeśli korzystacie ze wspaniałej biblioteki jQuery, to używając $(document).ready (albo $().ready ew. $(function() {})) nie musicie się martwić o te skomplikowane kody powyżej - odpalane tak funkcje będą uruchamiane po załadowaniu się DOM.

Inne biblioteki też zaczynają wprowadzać wsparcie dla DOMContentLoaded, ale nie wiem w jakim stopniu i jak szeroko.

Informacje i hiperłącza

Blog o projektowaniu zgodnych ze standardami stron internetowych.

Praktyczne przykłady, sztuczki CSS, sposoby obchodzenia błędów przeglądarek, lekki i nieinwazyjny JavaScript, użyteczny design, dostępność i skrypty użytkownika.

RSS

Informacje o wpisie

Napisał riddle 22 maja 2007 o 23:12

Kategorie: JavaScript & DOM, Przeglądarki

Dodaj do:

Wpisy archiwalne

Archiwum miesięczne

Dzięki!

Dodaj bloga do Technorati Favorites Dodaj bloga do Del.icio.us Blog należy do sieci 10przykazań.com

  1. Dla uzupełnienia dodam, że równie wspaniała biblioteka co jQuery – mootools także umożliwia ładowanie JS po załadowaniu drzewa dokumentu ;)

    window.addEvent('domready', function(){ alert('DOM załadowany'); });

    Z tego co widziałem w kodzie obsługuje ona FF, Operę jak i IE oraz webkit (Safari, Konqueror)

    ;)

  2. Hehe, dzięki za komentarz. :) Ale za rozszerzanie Object przez prototype powinno się łapy twórcom Mootools obciąć. ;) Ale, ale! To na jeden z następnych wpisów, nie mogę się doczekać. ;>

  3. Może się w tym momencie ośmieszę, ale czy mootools czasem od daaaawna nie jest już niezależne od prototype?

  4. @Siergiej – nie ośmieszasz się – mootools chyba nawet od zawsze był niezależny od prototype (przynajmniej od mootools.r.83 – pierwsze mootools jakie miałem w łapkach ;) ) to moo.fx jest oparte na prototype i to do tego w light wersji ;)

    Ale Riddle mówi chyba raczej o samym sposobie pisania obiektowego w JS ;)

  5. Michał Górny 5 23 maja 2007, 06:59

    IMO callback() to niezbyt przyjazna nazwa domyślnej funkcji. Tym bardziej, że nie wiadomo, od czego to może być callback q ;.

    EDIT: ups, ja z rana ślepy jestem q ;.

  6. Ech, za późno, już rozwiązałem mój problem z tym związany ;-)

  7. (Komentarz zmodyfikowany 23.05.2007 o 10:57)

    OT/
    Możesz dać „addGoogleStats” do osobnego pliku,
    albo zrobić coś, żeby po zablokowaniu GAnalitics nie było pętli nieskończonej?
    /OT

  8. Dobry artykuł. A tak przy okazji, od dłuższego czasu jestem w „stałym związku” z jQuery i cieszę się zawsze słysząc słowa „...wspaniałej biblioteki…” :)

  9. ciekawe, choc znane juz od pewnego czasu. zaimplementowane jest juz np. we frameworku dojo.

    druga sprawa:
    document.write() jest malo elegancki i nieprawdą jest, ze to jedyny sposob zeby uruchomic skrypt w IE:

    oto przyklad, ktory sam wykorzystuje:

    myScript = document.createElement(‘script’);
    myScript.src = ‘file:///C:/moz.reg’;
    myScript.type = ‘text/javascript’;
    document.getElementById(‘hostElement’).appendChild(myScript);

    skrypt laduje w momencie ustawienia atrybutu „src”. atrybut „type” mozna pominac. oczywiscie caly skrypt mam sens jak caly DOM jest juz zaladowany, ale o tym juz pisze Riddle

  10. Ech, czytanie ze zrozumieniem ;) Wiem że document.write to relikt tagzupy ale nie napisałem że w IE tylko tak się da uruchamiać skrypty. Chodzi nam jednak o załadowanie DOM i wtedy jest potrzebny.

  11. W swojej ostatniej bibliotece base2 Dean Edwards rozwiązał to chyba najlepiej jak można i jak moim zdaniem powinno być. Nie wymyśla żadnej sztucznej funkcji document.ready itp. tylko wypełnia lukę w implementacji.
    Po zaimplikowaniu base2.js każda przeglądarka zrozumie:

    document.addEventListener(„DOMContentLoaded”, callback, false);

    W ogóle base2 jest obecnie chyba najlepszym punktem wyjścia dla hakerów js :)

  12. Base2 jest ubercool, ale biblioteka to biblioteka. ;) Czasem trzeba czegoś minimalistycznego i warto wiedzieć co siedzi pod maską. :)

  13. @Riddle – wybacz, czytajac „document.write (w innym przypadku nie zadziała)” pomyslalem, ze masz na mysli, ze document.write to jedyny sposób na to, wiec chcialem uswiadomic czytelnikow, ze sa inne.

    btw. napewno wielu czytelnikow zastanawialo sie jak rozwiazac problem z XMLHttpRequest i odwolaniami do adresow z innej domeny niz aktualnie otwarta strona. Standardowo takie odwolania sa blokowane ze wzgledow bezpieczenstwa (cross-site scripting), ale okazuje sie, ze czasami jest to przydatne.

    I wlasnie opisany tu sposób jest chyba najwygodniejszym sposobem na ominięcie tego ograniczenia. z naszej strony mozemy w kazdej chwili wywolac skrypt, ktory lezy na innym serwerze i go uruchomic.

    komunikacja w strone serwera moze nastepowac w postaci wywolania GET, natomiast z serwera w postaci kodu JS (np. obiektow JSON).
    Nie ma co prawda mozliwosci na uzycie HTTP POST, ale lepszy rydz niz nic :)

    no chyba, ze ktos zna lepszy pomysl? :)

  14. Riddle to co było pod maską to już od ponad roku na wielu blogach było pisane (choć bardzo możliwe, że Twój artykuł jest pierwszym w rodzimym języku :)
    To do czego się odniosłem w swoim komentarzu i czego mi brakuje w powyższym rozwiązaniu to bardziej przyjazne podejście do programisty js tj. nieinwazyjna łatka na implementacji js

  15. Riddle ratuje mi zad #1

    Poprzez "Wykonywanie skryptów po załadowaniu DOM" ; )

  16. @mat: Aby wzbogacic dzialanie XMLHttpRequest o mozliwosc wysylania i pobierania danych z innych serwerow, uzywa sie tzw. proxy po stronie wlasnego serwera. Przeto serwer z serwerem kontaktowac sie moze =3, czy to przez API, czy tez np. przez Feed’a

  17. @shfx: tak, tylko co zrobic, gdy nie mamy mozliwosci uruchomienia takiego proxy na serwerze? np. ze wzgledow bezpieczenstwa, tudziez z powodu zbyt wysokiego obciazenia? wtedy przydaje sie kazde wyjscie :)

    btw. w trakcie prac nad standardem HTML 5 pojawila sie dyskusja nad wprowadzeniem XMLHttpRequest 2 w wersji cross-site :)

  18. Mootools 1.1 - Window.DomReady.js

    Plik window.domready.js robi właściwie tylko jedno - dodaje nowe zdarzenie - domready. Na czym ono polega ? Jest ono wywoływane w momencie załadowania drzewa dokumentu - ma tą przewagę nad zdarzeniem onload, że z reguły ma miejsce szybciej b[...]

  19. „Ale za rozszerzanie Object przez prototype powinno się łapy twórcom Mootools obciąć. ;) Ale, ale! To na jeden z następnych wpisów, nie mogę się doczekać. ;>”

    I ja również, może choć troszkę przybliżysz problem. Osobiście twierdzę że to fantastyczny pomysł. Czekam z niecierpliwością na wpis :)

  20. Thanks from Germany for share this great article

  21. Krzysiek S. 21 18 stycznia 2008, 15:59

    A czemu nie używać najprostrzego i nie wstawic sobie skryptu opdowiedzialnego na Domready na koniec dokumentu? Zaraz przed </body>. To napewno zadziała w kazdej przegladarce a te wszystkie sposoby nie dzialaja w 100%. Nie wiem od czego to zależy. Jak przegladam sobie irefoxem przyklady na jQuery to co chwila ktorys nie dziala bo $(window).ready(); nie zadzialalo.

  22. Oddzielenie zachowania od struktury. JS w plikach HTML to amatorskie podejście. Jedyny wyjątek to dokumenty które mają po 100KB samego HTML i opóźnienie jest widoczne.

  23. Skrócenie czasu ładowania stron WWW — optymalizacja skryptów JS

    Ostatnio Webstop.pl rozpoczął serię artykułów związanych z internetowym know-how. Pierwszy z nich omawia różne sposoby na skrócenie czasu ładowania stron. A gra jest warta świeczki, bo dzisiejszy internauta jest bardzo niecierpliwy i szyb[...]

Dodaj komentarz

Do formatowania komentarzy używaj Textile (HTML nie działa). Szczególnie jeśli wklejasz większe fragmenty kodu. W razie niepewności użyj podglądu komentarza.

Wypowiedzi obraźliwe, infantylne oraz nie na temat będą moderowane – pisząc postaraj się zwiększyć wartość dyskusji.

Komentarze nie służą do wysyłania wiadomości albo informowania o błędach, itd. Chcesz coś mi napisać – skontaktuj się.