Animații web native în context geo-spațial

Mai jos o definiție clasică:

HÁRTĂ, hărți, s. f. Reprezentare grafică în plan orizontal a suprafeței pământului (totală sau parțială), generalizată și micșorată conform unei anumite scări de proporție și întocmită pe baza unei proiecții cartografice. [dexonline.ro]

, urmată de o descriere personală al aceluiași subiect:

HÁRTĂ, hărți, s. f. Mod grafic de a combina și reprezenta dinamic, pe un substrat geo-spațial, seturi de date, povești și sentimente.

Profitând de creșterea puterii de procesare a masinilor de calcul, hărțile au evoluat dând naștere GIS-ului. Acesta este un nou mediu de prezentare a datelor ce a schimbat complet modul în care vedem și interacționăm cu spațiul înconjurător.

Hărțile au devenit deci la fel de accesibile și versatile ca Internetul, dar au moștenit și multe dintre defectele acestuia. Web-ul este un mediu dinamic, colorat și interactiv iar hărțile noastre pot fi la fel! Doar că fiecare funcționalitate costă (mai ales în mediul mobil): consumul de date este limitat de costuri iar funcționalitățile sunt sugrumate de resursele hardware. Cu alte cuvinte trebuie să fim cumpătați.

Internetul este o mare Piață în care prima impresie face diferența dintre popularitate și obscuritate. Și ne dorim ca hărțile noastre să fie accesate, citite, puricate, criticate și distribuite. Pentru a atrage atenție putem să ne conformăm trend-urilor, iar trendul actual este: animația.

De ce animăm?

Animația este un bun mod de a oferi feedback utilizatorului. Cu ajutorul unei animații putem să extragem un subiect dintr-o aglomerare de puncte, putem să garantăm/validăm succesul interacțiunii dintre hartă și utilizator, putem să prioritizăm prezentarea unui eveniment (o urgență) ce necesită neapărată atenție dar putem foarte ușor să și sărim calul .

Trebuie să ne asigurăm că animația are un scop precis, aduce un plus de lizibilitate sau interactivitate hărții și nu este făcută doar pentru că “este mișto”.

Web-ul abundă de resurse. Librări externe precum Greensock sau jQuery ne oferă metode de animare, doar că…. sunt resurse adiționale. Asta înseamă că pe lângă librăria de generare a hărții web, funcțiile și stilurile personalizate, diferitele basemap-uri sau baze de date mai trebuie să includem și alte fișisere care se vor ocupa doar de animare (poate uneori a unui singur punct pe hartă), ceea ce poate fi considerat un cost inutil.

Am o veste bună și una rea.

Vestea bună: nu trebuie neapărat să depindem de o librărie externă ce se va ocupa de animații. De multe ori putem recurge la banalul CSS. Ba chiar putem folosi și SVG, acel format de imagine vectorial ce se randează impecabil pe orice dimensiune de ecran.

Un avantaj al SVG-ului este că poate fi animat direct cu SMIL. Acest limbaj este nativ recunoscut de browsere, la fel ca si svg-ul pe care îl însoțește, deci nu mai avem nevoie de nici o resursă externă pentru a anima. Dar …[ urmează partea rea]:

Vestea rea: așa cum prezentam într-un articol pe blogul personal, acest SMIL a fost abandonat de Chrome, un mare jucator pe piața browserelor. Mulți au fost nemulțumiți sau chiar revoltați, dar le dau dreptate celor de la Google: SMIL nu era tocmai uniform implementat de către producătorii de browsere iar limbajul lui stângaci și greoi a pierdut rapid teren in fața mult mai versatilului CSS Animations.

Veste bună, veste rea: episodul 2

Vestea bună: ca urmare a abandonării SMIL de către giganul Google, furnizorii de browsere s-au pus de acord întru a dezvolta un nou limbaj, nativ browserului, ce va conține un API extins de animații cu o sintaxă mult mai accesibilă (derivată din curentul CSS Animations).

Vestea rea: pagina de documentații a acestui nou API de animare anunță: “această documentație este un ciot”. Web Animations API (WAAPI) este încă în dezvoltare. Ca plajă de suport la momentul redactării articolului: Chrome au implementat deja, Mozilla permit folosirea lui doar după activarea unor funcții experimentale din setări iar restul …. mai așteaptă.

Cu toate acestea, pe voi cei care folosiți Chrome si sunteți curioși de cum va arăta viitorul cu un limbaj nativ de animare, vă invit să încercăm câteva exemple de animare pe harta web al unor elemente svg cu ajutorul WAAPI-ului.

atentie urmeaza continut tehnic!

Cum animăm?

Pasul #1. Definim obiectul ce urmează a fi animat

WAAPI va aștepta ca noi să îi oferim un obiect din DOM pe care îl va manipula. Vom folosi o iconiță SVG cu un identificator (clasă sau id) reprezentativ.

Un mod util de a utiliza SVG cu Leaflet (librăria web-map) este să injectăm imaginea direct în declarația iconiței:

var cssIcon = L.divIcon({ 
 className: 'css-icon', // clasă pe care o putem folosi.
 html: 'codul-svg-intră-aici', // codul svg-ului, comprimat pe un rând
 iconSize: [50,50], // alte proprietăți
 iconAnchor: [50,50]
});

De reținut: când animăm un singur punct pe hartă putem folosi un ID pentru a chema obiectul dorit:

obiectulMeu = document.getElementById('svg-id')

Dacă dorim să utilizăm această iconiță pentru mai multe puncte atunci un getElementById ar aduce doar primul din lista dorită. Pentru a anima mai multe puncte ce împart aceeași iconiță trebuie să chemăm obiectele folosind o clasă:

var obiecteleMele = document.getElementsByClassName("svg-class");

Pasul #2. Definirea player-ului

Playerul este obiectul WAAPI ce se va ocupa de animație.

var player = document.getElementById('svg-id').animate()

Obiectul „player” reprezintă iconița selectată căreia ii atașăm metoda „.animate()”.

Asta este tot! Pe variabila „player” potem mai târziu chema comenzi de tipul stop, play, repeat, reverse, etc.

Pasul #3. Comenzile de animare

În interiorul lui animate() vom scrie stările prin care va trece iconița nostră urmate de setări generale ale animației.
Obiect = ceva.animate( [ {stare 1},{stare 2},{stare n} ] , {durata, directie, etc})

Un exemplu funcțional arată așa:

var player = document.getElementById('svg-id').animate(
 [{transform: 'scale(0)', opacity: 1},
 {transform: 'scale(2)', opacity: .7}],
 {duration: 700, //milliseconds
 easing: 'ease-in-out',
 delay: 1,
 iterations: Infinity,
 direction: 'normal',
 fill: 'auto'}
);

Player-ul va începe să ruleze într-o secvență repetată de infinite ori și va trece prin două stări.SVG-ul cu id “svg-id” își va dubla dimensiunile și va deveni mai transparent, apoi va reveni la mărime 0 în timp ce va deveni mai opac ( opacity 1 = 100% vizibil, opacity 0 = 0% vizibil).

Animația de mai sus poate fi folosită pentru a anunța un cutremur.

Aceasta a fost o animație a unui sigur obiect de pe hartă.  În scenariul următor încarcăm în hartă, dintr-un geojson (sau dinamic, într-un scenariu real), puncte de tip “ambulanță” sau “mașină de intervenție”. Ambele clase pot avea status-ul “liber” sau “rezervat”. Inițial toate punctele apar pe hartă cu simbolul unui punct roșu, ce va fi alterat și animat la nevoie.

Pentru a putea anima diferit obiectele trebuie să le atribuim o clasă. Mai întâi pregătim un array (listă) gol unde încărcăm punctele din geojson.

pointArray = [];

Vom itera prin fiecare obiect din geojson cu funcția disponibilă în Leaflet:

var geojsonLayer = L.geoJson(masini, {
 pointToLayer: function(feature, latlng) {
 var marker = new L.Marker(latlng);
 pointArray.push(marker);
 return marker;}
});

A se nota utilizarea lui pointToLayer. Toată această funcție citește fiecare obiect din geojson (masina), îl transformă în marker apoi îl adaugă array-ului anterior creat. Din array putem selecta, sorta și adăuga clase marker-elor:

Cu un banal for loop citim array-ul conform logicii următoare:

  • Dacă este ambulanță atunci are status liber?
    • Dacă este liber atunci adaugăm iconiță de clasă “freeAmbulance”
    • Dacă nu este liber atunci este rezervat deci adaugăm iconiță de clasă “usedAmbulance”
  • Dacă nu este ambulanță atunci este intervenție. Are status liber?
    • Dacă este liber atunci adaugăm iconița de clasă “freeIntervention”
    • Dacă nu este liber atunci este rezervat deci adaugăm iconița de clasă “usedIntervention”

Codul in sine arată astfel:

 
for (var i=0; i < pointArray.length; i++){
 if (pointArray[i].feature.properties.tip_util === "ambulanta"){
 if(pointArray[i].feature.properties.status === "liber"){
 var marker2 = new L.Marker(pointArray[i]._latlng, {
 icon: freeAmbulance // adaugă iconița 
 draggable:'true'})
 .bindPopup(pointArray[i].feature.properties.tip_util).addTo(map);
 }else {
 var marker2 = new L.Marker(pointArray[i]._latlng, {
 icon: usedAmbulance}).bindPopup(pointArray[i].feature.properties.tip_util).addTo(map);} 
 }else {
 if(pointArray[i].feature.properties.status === "liber"){
 var marker3 = new L.Marker(pointArray[i]._latlng, {
 icon: freeIntervention,
 draggable:'true'})
 .bindPopup(pointArray[i].feature.properties.tip_util).addTo(map);}
 else {
 var marker3 = new L.Marker(pointArray[i]._latlng, {
 icon: interventionClass2}).bindPopup(pointArray[i].feature.properties.tip_util).addTo(map);}}
;}

Acum că fiecare tip de marker are o iconiță cu clasă proprie putem să animăm. Spre exemplu vom anima doar mașinile de intervenție cu status liber prin:

function interventiiAnim(){
 for (j=0; j < 17; j++){
 var player = document.getElementsByClassName("freeIntervention")[j].getElementsByTagName('svg')[0].animate([
 {transform: 'scale(1)'},
 {transform: 'scale(2.5)'}],
 {duration: 700, // millisecunde
 easing: 'ease-in-out', // linear, bezier, etc.
 delay: 1, // millisecunde
 iterations: Infinity, // sau număr
 direction: 'alternate', // normal, reverse, etc.
 fill: 'auto' // backwards, both, none, auto
});};

Fiecare clasă va primi un player propriu. Codul pe de-a-ntregul poate fi analizat pe paginea mea de github.

Animațiile sunt extrem de banale dar scopul meu a fost să creez un proof of concept. Pe măsură ce WAAPI-ul se dezvoltă vom putea folosi metode din ce în ce mai sofisticate iar interfețele noastre vor fi din de în ce mai plăcute și utile. Animația nu mai este doar un trend, este o necesitate!

Celor care au rezistat până la finalul acestui articol [sau au sărit pur și simplu peste], vă ofer un mic montaj ce prezintă exact harta generată de codul scris anterior, dar într-un scenariu aparte:

Publicat de

Stanciu Bogdan Mircea

Mircea îmi este numele, programator îmi este meseria. Am trecut cândva prin domeniul geografiei (facultate și primul job major), dar am ales să pornesc pe calea programării. Lucrez cu Python cel mai des, in rest Front-End si ceva design când este nevoie. Nu sunt cel mai strălucit dintre oameni, dar îmi dau silința.

Începe discuția pe devforum.ro