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%.
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ń).
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).
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.
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).
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).
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()
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.
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).
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; }
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).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.
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...
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 ;-).
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.
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.
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.
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.
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.
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:
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).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 :-).