IE8 dogorywa – radujcie się

IE8 dogorywa

Półtora roku temu napisałem po angielsku artykuł IE8 must die! Chciałem przekonać nim, że warto powalczyć o zagrzebanie IE8. Wówczas miał on jeszcze 4% polskiego runku. Potem następowały różne fluktuacje, zwłaszcza w okresie wakacyjnym, ale dzisiaj jest już pewność – od miesiąca Internet Explorer 8.0 utrzymuje się poniżej 1%.

Od dzisiaj wszystko się zmieni?

Zacznę przewrotnie od drobnego schłodzenia głowy. Wiadomość o upadku IE8 nie powinna mieć dużego znaczenia dla twórców witryn internetowych nastawionych głównie na prezentowanie treści. Siłą stron internetowych jest to, że bez specjalnego wysiłku ze strony twórców działają one na dowolnych urządzenia, w praktycznie dowolnej przeglądarce. Nie należy zatem opierać kluczowej roli jaką jest prezentacja artykułu na funkcjach, które działają tylko w nowych przeglądarkach.

Druga kwestia, to odpowiednia interpretacja tego „1%” (ściślej „0,9%”). To statystyka używanych przeglądarek oparta na odsłonach badanych przez firmę Gemius. Nie koniecznie, a nawet prawie na pewno nie będzie to dokładnie odzwierciedlać preferencji twoich użytkowników. Ze spadku IE8 w okresie wakacyjnym można wysnuć wniosek, że w pracy ludzie częściej korzystają z przestarzałych przeglądarek. Są też całe grupy osób, które dużo częściej muszą zadowolić się starym komputerem i starym systemem operacyjnym – tak jest zwłaszcza w sektorze publicznym. Jednym słowem warto sprawdzić własną grupę docelową zanim podejmie się drastyczne decyzje.

Warto też zwrócić uwagę, że na świecie IE8 wciąż ma jeszcze ponad 2% i ostatnio spadek jego popularności nieco wyhamował (vide zebrane statystyki ze świata i z Polski – aktualizowane co tydzień).

Czy mogę już używać SVG?

Do ilustrowania artykułów – nie wprost. Tak jak mówiłem, tam gdzie mówimy o prostych treściach, warto trzymać się w ogonie, żeby dotrzeć do jak najszerszego grona odbiorców. Lepiej zatem używać SVG do ilustrowania artykułów tylko wtedy gdy ma się możliwość przetworzenia SVG po stronie serwera na PNG (tak jak od lat robi to Wikipedia – patrz: Manual:Image_administration#SVG).

Do ikonek, które wzbogacają tekst – śmiało. Nawet przeglądarki mobilne wspierają wyświetlanie SVG. Warto jedynie pamiętać, żeby podawać wysokość i szerokość przy zagnieżdżaniu obrazków SVG inaczej mogą się one dziwnie wyświetlać (np. na starych Androidach).

SVG zamiast ikon

Możemy zatem spokojnie używać SVG do ikon będących dodatkiem do tekstu:

<a onclick="graj()">
    <img src="graj.svg" width="15px" height="15px" alt=" "> Odtwarzaj
</a>
<a onclick="stop()">
    <img src="stop.svg" width="15px" height="15px" alt=" "> Zatrzymaj
</a>

Warto przy okazji pamiętać, że alt używamy tak samo jak dla innych obrazków. Tutaj zostawiamy pusty alt, bo obrazek nie dodaje nic do treści.

SVG w tle

Można też używać SVG w tle poprzez CSS. Np. w tle strony:

body {
    background: url(glowne-tlo.svg);
}

Wcześniejszy przykład z ikonami odtwarzania i zatrzymania można również zastąpić SVG dołączonym w tle.

<a onclick="graj()" class="ikona graj">Odtwarzaj</a>
<a onclick="stop()" class="ikona stop">Zatrzymaj</a>
a.ikona {
    padding-left: 18px;
    background-repeat: no-repeat;
    background-position: left center;
}
a.ikona.graj {
    background-image: url(graj.svg) ;
}
a.ikona.stop {
    background-image: url(stop.svg);
}

Uwaga! SVG w tle nie będzie działać poprawnie w starych Android (2.3). Czym również nie należy się przejmować jeśli obrazek jest jedynie ozdobnikiem (jak w powyższym przykładzie).

Animacje

Należy zapomnieć o używaniu prostych animacji w SVG. Mam na myśli animacje SMIL tworzone za pomocą elementów animateTransform. Edge nie będzie ich obsługiwał, a od niedawna Chrome wyświetla ostrzeżenie w konsoli, że przestaną one być obsługiwane.

To nie oznacza, że animacje w SVG nie będą w ogóle wspierane. Trzeba jedynie zastosować animacje za pomocą requestAnimationFrame, czyli przez JavaScript. Zobacz przykład obracania kwadratu za pomocą requestAnimationFrame (MSDN). Więcej przykładów w artykule MSDN: Basic SVG animation (jest tam też przykład dla animacji SMIL).

Co nowego w CSS?

Selektory...

Duuużo selektorów. OK, to, że mamy teraz do wyboru dużo selektorów nie jest kwestią samego spadku popularności IE8, a raczej tego, że IE9 również nie jest zbyt popularny, a o IE6 i IE7 właściwie nikt już nie pamięta.

Jeśli do tej pory używaliście głównie selektorów klas (.klasa) i id (#id), to warto poznać szersze możliwości CSS. Odsyłam w tej kwestii do mojego artykułu o tajemniczym tytule „Selektory CSS”.

Calc – obliczenia w CSS

calc() to funkcja pozwalająca wykonywać obliczenia bezpośrednio w CSS. Nie chodzi tu jednak o to co można sobie zrobić w LESS czy SASS. Nie, calc(10px - 5px) nie jest zbyt ciekawe... Istotą tej funkcji jest to, że pozwala mieszać jednostki!

Można zatem użyć width:calc(100% - 200px) co pozwala uprościć tworzenie zaawansowanych układów strony. Tu jednak z uporem maniaka przypomnę, że dla kluczowych fragmentów strony warto zadbać o łagodny upadek nieszczęśników korzystających ze starych przeglądarek. Na szczęście jest darmowy zastępnik dla starych przeglądarek – PolyCalc.

Przeźroczystość

Część osób już pewnie nie pamięta filter. To było coś co wymyślił Microsoft i na szczęście odeszło w niepamięć. Teraz możemy już wszyscy używać opacity do określania stopnia przeźroczystości elementów (np. input:disabled {opacity:0.9}).

Więcej swobody daje rgba(), czyli RGB z przeźroczystością („a” to kanał alfa określający przeźroczystość). Istotną różnicą między rgba i opacity jest to, że stosując rgba stosujemy przeźroczystość do konkretnego fragmentu (np. ramki lub tła konkretnego elementu), natomiast opacity automatycznie działa na wszystkie elementy podrzędne (ramkę, tło, tekst i wszystko inne).

Wielokrotne tła

Tak, tak. Wszystkie właściwości CSS dla tła pozwalają na podanie po przecinku informacji o drugim (i kolejnym) tle.

Jako, że tło rzadko stanowi istotę strony, możemy spokojnie z tego korzystać i na przykład dodać cudzysłowy dla blockquote

blockquote {
    background-image: url(lewy-cudzyslow.svg), url(prawy-cudzyslow.svg);
    background-position: left top, right bottom;
    background-repeat: no-repeat;
    padding: 1em;
}

Obroty i inne ozdobniki

  • transform: rotate(45deg) i inne (statyczne) przekształcenia. Zobacz przykłady na css3files.com.
  • border-radius – właściwość pozwalająca określić promień zaokrąglenia prostokąta elementów.
  • box-shadow – dodanie cienia do elementu; ostatnio popularna metoda wyróżnienia głównie za sprawą „Material Design” z Android 5.0.
  • background-size – pozwala przeskalować obrazki tła (szczególnie ważne dla SVG).

Nowa era JavaScript?

Na koniec zostawiłem wisienkę, czyli zmiany w JavaScript. Gdzieś na wyciągnie ręki wydaje się być nowa edycja JS, czyli ES6/ES2015, a tymczasem do tej pory często męczymy się z rzeczami, które w większości przeglądarek są dostępne od wielu lat. Tutaj problemem jest niestety zarówno IE8 jak i IE9. Razem mają jednak obecnie ok. 1,5%, więc to też nie jest dużo.

Popularne funkcje dostępne nawet w IE8

Warto pamiętać, że element.querySelector() i element.querySelectorAll() są dostępne nawet w IE8. Pierwsza funkcja umożliwia użycie selektorów CSS do pobrania pierwszego dopasowanego elementu (zawsze zwraca jeden element), natomiast querySelectorAll zawsze zwraca listę elementów. Także jedna z najpopularniejszych części jQuery jest już dostępna w zasadzie wszędzie (IE6 i IE7 praktycznie nie istnieją). Tu warto przypomnieć, że jQuery korzysta z nowych funkcji jeśli może. W jQuery 2.0 wyrzucono nawet starą implementację (i parę innych starych obejść), co znacząco (ok. 10%) zmniejszyło objętość tej biblioteki. Zwrócono mi uwagę, że to nie do końca jest prawda. To znaczy prawdą jest spadek objętości jQuery 2.0, ale nie udało się wyrzucić odpowiednika querySelectorAll... Niestety niektóre selektory w natywnej wersji działają nieprawidłowo (np. tag#id + cokolwiek) i w takich przypadkach jQuery przerzuca się na swoją starą implementację selektorów.

Podobnie mamy już dostęp do natywnego JSON.stringify() oraz JSON.parse(). Można zatem zupełnie zrezygnować z popularnej biblioteki używanej główniej do obsługi komunikacji z serwerem (REST).

Obsługa JSON przydaje się także do obsługi lokalnego zapisu obiektów danych do sessionStorage lub localStorage. Sam zapis danych jest również dostępny już w IE8. Warto przy tym pamiętać, że zapis np. do localStorage to nie tylko zapisywanie danych, ale także możliwość komunikacji między zakładkami, a nawet oknami przeglądarki dzięki zdarzeniu storage... Tu jednak przechodzimy do braków IE8...

Obsługa zdarzeń (IE9+)

IE8 obsługuje zdarzenia, ale w starej wersji czyli przez obiekt.attachEvent(...). O tym możemy powoli zapomnieć. Począwszy od IE9 mamy dostęp do standardowej obsługi zdarzeń czyli obiekt.addEventListener(...).

Pozwolę sobie tu na dygresję dotyczącą obsługi komunikacji miedzy oknami. Powiedzmy, że szukam jakichś informacji. Otwieram dużo zakładek z jakimś blogiem. Loguję się na jednej ze stron, ale pozostałe zakładki o tym nie wiedzą – muszę odświeżyć stronę, żeby móc np. komentować. Nie musi tak być.

var sesjaGlobalna = new function() {
    // ...tu oczywiście inne metody...
 
    // metoda wywoływana po logowaniu
    this.ustawDaneSesji = function (daneUzytkownika) {
        var daneSesji = {
            zalogowany:true,
            login:daneUzytkownika.login,
            awatar:daneUzytkownika.awatar
        };
        this._daneSesji = daneSesji;
        localStorage.setItem('sesja', JSON.stringify(daneSesji));
    }
 
    // odbiór danych sesji z innych okien/zakładek
    window.addEventListener("storage", function(zdarzenie){
        if (zdarzenie.key !== 'sesja') {
            return;
        }
        daneSesji = JSON.parse(localStorage.getItem('sesja'));
        // tu sprawdzamy, czy zmienił się stan wg wewnętrznych zmiannych
        // jeśli stan się zmienił to możemy je wysłać do widżetu komentarzy 
        sesjaGlobalna.zastosujNoweDaneSesji(daneSesji);
    }, false);
}

Wiem, że to nie jest najprostszy przykład addEventListener, ale mam nadzieję, że ciekawy ;-).

Wyszukiwanie w tablicy i przecinki (IE9+)

Schodzimy na ziemię. Mamy prostą tablicę z danymi i chcemy sprawdzić, czy dana wartość w niej istnieje. Od IE9 mamy dostęp do Array.indexOf (odpowiednik String.indexOf). Proste, szybkie... ale w IE8 trzeba się bawić z pętlami.

Druga sprawa, to kwestia deklaracji tablic. To nie tyle funkcja, co właściwość parsera. W IE9 (i wszystkich innych przeglądarkach) zapis [1,2,] jest tożsamy z [1,2]. Stosowanie przecinka w każdej linii jest oczywiście bardzo wygodne zwłaszcza przy długich tablicach i obiektach konfiguracyjnych. Jedyne co nas ostrzega przed tym problemem to niestety tylko IDE (np. Netbeans) i różne inne sprawdzacze składni (JSLint i pokrewne).

W obu przypadkach przestrzegam przed nadużywaniem tych możliwości. Zarówno użycie indexOf jak i przecinka na końcu tablicy/obiektu spowoduje błąd w IE. To z kolei sprawi, że JS w ogóle nie będzie działał. Jeśli wasza witryna działa z grubsza bez JS, albo jest z natury eksperymentalna, to w porządku. Jeśli ładowanie treści jest zależne od JS, to nie polecam.

Lokalizacja użytkownika (IE9+)

Z jednej strony nie ma obejścia dla braku Geolocation, z drugiej strony można spokojnie stosować go nawet mając klientów z IE8. To typowy element wzbogacania serwisu. Co więcej użytkownik dostaje jasny komunikat i może nie zezwolić na użycie jego lokalizacji jeśli nie chce, żeby witryna wiedziała gdzie jest.

Warto wiedzieć, że można określić lokalizację użytkownika także wtedy jeśli ma WiFi (np. w laptopie). Oczywiście dokładność jest mniejsza niż z GPS, ale na podstawie widocznych sieci WiFi przeglądarki określają lokalizację z dokładnością do ok. 100 metrów (co spokojnie wystarczy np. do podania pogody z okolicy). Na komputerze bez WiFi przeglądarki też czasem sobie radzą, ale to już tylko z dokładnością do miasta.

Poniżej prosty przykład pobierania lokalizacji użytkownika i pokazania jej na mapce.

 // pobranie i pokazanie lokalizacji użytkownika
    function pokazMapke() {
        if (!geoHelper.available) {
            return false;
        }
        var mapka = document.getElementById('mapka');
        var googleMapsUrl = 'http://maps.google.com/maps/api/staticmap'
            +'?center=%%lat%%,%%lon%%'
            +'&markers=%%lat%%,%%lon%%'
            +'&maptype=mobile&sensor=false&zoom=10&size=200x200'
        ;
        geoHelper.initGet(function(location){
            mapka.src = googleMapsUrl
                .replace(/%%lat%%/g, location.coords.latitude)
                .replace(/%%lon%%/g, location.coords.longitude)
            ;
        });
        return true;
    }

Oczywiście trzeba jeszcze zaimplementować małą klasę pomocniczą:

 var geoHelper = new function() {
        // sprawdzenie dostępności
        this.available = false;
        if (typeof(navigator) != 'undefined' 
            && typeof(navigator.geolocation) != 'undefined' 
            && typeof(navigator.geolocation.getCurrentPosition) == 'function')
        {
            this.available = true;
        }
 
        // zlecenie pobrania danych
        this.initGet = function(onSuccess)
        {
            if (this.available)
            {
                navigator.geolocation.getCurrentPosition(onSuccess, this.errorHandler);
                return true;
            }
            return false;
        };
 
        // podstawowa obsługa błędów
        this.errorHandler = function(error)
        {
            // jeśli użytkownik odmówił to nie jest błąd
            if (error.code != error.PERMISSION_DENIED)
            {
                alert("Nie udało się pobrać Twojej lokalizacji.");
            }
        };
    }

Jest jeden haczyk – pobieranie lokalizacji nie działa na lokalnych plikach (file://...). Nie wiem dlaczego. To znaczy rozumiem, że nie ma tu „domeny”, ale przeglądarka mogłaby po prostu pytać przy każdym pliku o pozwolenie. Na dzień dzisiejszy Chrome nie pyta tylko od razu odmawia za użytkownika.

Rysowanie w przeglądarce (IE9+)

Kolejne API dostępne od IE9, to Canvas API, czyli płótno do rysowania, ale też do operacji na obrazkach. Można zatem zarówno narysować obrazek (z pikseli, nie wektorowo) jak i załadować obrazek (bitmapę) oraz wykonać operacje typu skalowanie, odwracanie kolorów itp. W skrajnym wypadku można nawet tworzyć gry (patrz: Building Atari with CreateJS).

Dobrym przykładem na wzbogacenie interakcji użytkownika ze zwykłą stroną jest przetworzenie obrazka, który ma być np. awatarem. Użytkownik IE8 może go przyciąć przed wysłaniem, ale już z IE9 możemy dać możliwość przycięcia go w przeglądarce do określonych proporcji (zwykle awatary są kwadratowe). Moglibyśmy przyciąć obraz automatycznie po stronie serwera, ale oczywiście niekoniecznie da to dobry efekt (zawsze jest pytanie, czy przyciąć z jednej strony, czy wyśrodkować). Można zatem po przesłaniu pliku wyświetlić kanwę, na której użytkownik narysuje kwadrat określający granice.

Postępowe przesyłanie plików (IE10+)

Dawniej do pokazania postępu przesyłanego pliku potrzebne były albo aplety Java, albo Flash itp. Teraz, dzięki File API, jest to znacznie prostsze. Wystarczy obsłużyć zdarzenie progress na obiekcie xhr.upload (gdzie xhr to AJAX-owe żądanie). Zdarzenie to dostarcza informacje o liczbie przesłanych bajtów i rozmiarze pliku.

Stosunkowo łatwo jest tutaj stworzyć fallback – wystarczy sprawdzić czy jedna z klas File API jest obsługiwana np. sprawdzając var isProgressAvailable = (typeof FileReader !== "undefined"). W wypadku gdy wiemy, że nie możemy obsłużyć postępu po prostu ukrywamy pasek postępu i gotowe – mamy zwykły formularz do przesyłania plików.

Obróbka obrazków przed wysyłką (IE10+)

Mając dostęp do File API możemy zrobić znacznie więcej, bo mamy dostęp nie tylko do postępu, ale do danych samego pliku przed wysłaniem na serwer. Łącząc to z Canvas API możemy dać możliwość użytkownikowi przycięcia pliku jeszcze przed jego przesłaniem.

Dwa przykłady użycia File API i Canvas API do pokazania obrazka zamieściłem na JS Fiddle:

Zaznaczam, że oba przykłady działają bez dotykania serwera. Przycięcie obrazka to już kwestia dorobienia obsługi touchstart/touchend i – w ramach informacji zwrotnej dla użytkownika – narysowania ramki.

Animacje, czaty, wiadomości CORS (IE10+)

Różnych API niedostępnych w IE8 lub IE9 jest znacznie więcej, ale nie sposób opisać wszystkie w jednym artykule. Nie chcę też rozpisywać się o rzeczach, które trudno wykorzystać do wzbogacenia strony. Wymienię zatem jeszcze tylko parę dostępnych od IE10:

  • Funkcja requestAnimationFrame bardzo poprawia wydajność i płynność działania animacji. Zasadniczo używa się tego zamiast rekurencyjnego wywołania setTimeout. Funkcji można używać zarówno do animowania SVG, jak i Canvas, a nawet zwykłych elementów HTML (chociaż do HTML warto rozważyć użycie animacji CSS).
  • WebSockets, to API umożliwiające stworzenie m.in. czatów, czy powiadomień z serwera. Sednem działania socketów jest stałe połączenie z serwerem umożliwiające wysyłanie i odbieranie komunikatów na bieżąco.
  • MessageChannel umożliwia bezpieczną komunikację między domenami (CORS). Osobiście używałem tego do kopiowania formularzy między dwoma ramkami (m.in. z użyciem biblioteki pomocniczej FramePostman). Mógłbym pokazać klasę do kopiowania formularzy, ale jest skomplikowana i brzydka ;-). Zresztą większość roboty przy kopiowaniu formularzy jest z pobraniem i ustawieniem wartości różnych rodzajów pól, więc z pomocą jQuery dałoby się to znacząco skrócić.

Podsumowanie

Mam nadzieję, że po przeczytaniu tego artykułu chociaż jedna osoba zastosuje w swojej aplikacji nie tyle nowe możliwości, co nowe możliwości z fallbackiem. Nowe standardy są fajne i to dobrze, że nowe przeglądarki wypierają stare, ale ślepe parcie do przodu nie jest zgodne z duchem Internetu i stron WWW. Świat jest szeroki i różnorodny, więc idźcie i czyńcie go bardziej różnorodnym... i dostępnym :-).