W dzisiejszym wpisie pokrótce omówię tworzenie interaktywnej mapy obrazków z użyciem rzadko używanego elementu MAP. Przy okazji stworzymy kompletny, praktyczny przykład do wykorzystania na dowolnej stronie. Przejdźmy zatem do konkretów!
Założenia projektu
Nie tak dawno temu potrzebowałem stworzyć mapę Europy, której państwa mogą przyjmować trzy stany:
- nieaktywne, czyli kiedy państwo jest nieklikalne / nieaktywne,
- domyślne, kiedy dane państwo jest możliwe do kliknięcia, ale użytkownik nie umieścił nad nim myszki,
- aktywne – w momencie kiedy na klikalne państwo ktoś najechał myszką.
Na co mi to? Musiałem stworzyć mapkę, w której tylko część państw jest klikalna i posiada stan :hover oraz jest klikalna/odsyła do innej sekcji.
Efekt końcowy natomiast wygląda następująco:
Interaktywna mapa Europy – pokaż przykład
Kodzimy!
Kiedy już wiemy co chcemy zrobić, zabierzmy się za robotę. Do w pełni działającej mapki będziemy potrzebowali oczywiście HTML i CSS, ale również minimalnej ilości JavaScriptu. Zacznijmy od HTML i stwórzmy kontenery, w którym będzie nasza mapka:
1 2 3 4 5 | <div class="map-container"> <div class="map europe"> <!-- tutaj będzie kod mapy --> </div> </div> |
Kontener nadrzędny będzie zawierał border i paddingi, zatem stworzyliśmy jeszcze jeden, któremu damy overflow: hidden nie pozwalając tym samym, by treści większe niż kontener wyszły za swoje pole.
Do kontenera z mapą będziemy musieli wstawić kilka elementów:
-
Obrazek z domyślnym widokiem mapy.
1<img class="background" src="images/map_europe_default.png" alt="">
Tak jak wspomniałem, mapa musi pozwalać by dowolne państwa były nieaktywne i nieklikalne. Możemy to osiągnąć w bardzo prosty sposób – wystarczy wstawić obrazek z mapą, gdzie wszystkie państwa mają stan nieaktywny. Obrazek ten musi mieć taką samą wielkość jak kontener mapy. W dodatku ustawiamy mu najniższy indeks:
1.map .background {position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1}Nasz obrazek prezentuje się następująco:
-
Lista z państwami, np.
1
2
3
4
5
6<ul class="countries">
<li class="germany"><a href="#germany">Niemcy</a></li>
<li class="spain"><a href="#spain">Hiszpania</a></li>
<li class="poland"><a href="#poland">Polska</a></li>
<li class="sweden"><a href="#sweden">Szwecja</a></li>
</ul>Lista ta posiada tyle elementów, ile jest klikalnych państw na naszej mapie (klikalnych przez zdefiniowanie elementów AREA). Każdy z tych elementów zawiera odsyłacz – nie będziemy się opierać na odsyłaczach z AREA, bowiem dla niektórych państw zdefiniujemy kilka obszarów (np. państwa wyspiarskie).
Dla każdego państwa z listy musimy stworzyć obrazek w dwóch stanach: default i hover. Widok mapy dla każdego stanu musi być wielkości całej mapy i prezentować tylko dane państwo. Jeśli zatem potrzebujemy dwóch stanów to obrazek będzie dwukrotnie większy niż mapa (najlepiej oprzeć go na CSS Sprites by zminimalizować liczbę zapytań HTTP). Przykład takiego obrazka dla Polski:
Co ważne, każdy z tych elementów ma pozycję absolutną i jest rozciągnięty na całą mapkę. Ponadto posiada indeks o wartości większej niż obrazek z mapą w stanie nieaktywnym. Pozwoli to nałożyć widok państwa możliwego do kliknięcia na państwa nieaktywne.
Każde państwo otrzyma więc następujące style:
1
2
3
4
5.map .countries>li {position: absolute; top: 0; right: 0; left: 0; bottom: 0; z-index: 2; margin: 0; padding: 0; background: transparent none no-repeat 0 0; text-indent: -9999em}
.map .countries>.albania {background-image: url('../images/countries/europe/albania.png')}
.map .countries>.andorra {background-image: url('../images/countries/europe/andorra.png')}
.map .countries>.austria {background-image: url('../images/countries/europe/austria.png')}
/* etc... */Kolejny stan, :hover jest bardzo łatwy do osiągnięcia – wystarczy kawałek kodu CSS:
1.map .countries>li.hover {background-position: -920px 0}Z racji wcześniejszego użycia CSS Sprites nie musimy dla każdego państwa definiować nowej ścieżki do pliku w wersji aktywnej – wystarczy wyświetlić obrazek w innej pozycji. Prosto i przyjemnie.
-
Przeźroczysty obrazek z podpiętym elementem mapy
Najważniejszym i brakującym elementem mapy jest sam plik graficzny z podpiętym elementem MAP i zdefiniowanymi obszarami AREA:1
2
3
4
5
6
7
8
9<img usemap="#map" src="images/map_transparent.png" alt="">
<map name="map">
<area href="#iceland" alt="" shape="poly" coords="239,90,248,87,260,82,265,96,270,105,271,116,267,128,263,138,250,136,241,146,228,141,220,146,190,144,171,128,167,118,153,109,149,107,152,102,170,98,170,83,158,78,157,73,183,82,177,74,190,72,166,59,172,52,183,48,193,57,192,45,201,43,203,61,193,82,204,79,213,71,217,82,224,74,230,79,230,90,238,81">
<area href="#germany" alt="" shape="poly" coords="548,518,558,524,559,533,566,535,566,552,561,559,572,570,572,609,569,615,563,611,530,629,517,624,529,650,549,678,541,680,530,688,531,699,530,710,525,704,498,704,485,701,479,707,465,700,461,704,452,697,424,694,424,680,426,667,435,657,427,652,408,638,410,625,407,615,407,600,411,588,413,575,427,571,442,573,441,559,443,547,444,535,444,526,463,525,468,537,471,526,479,525,473,510,481,506,478,491,491,495,495,508,507,514,516,515,512,523">
<area href="#lichtenstein" alt="" shape="poly" coords="461,705,464,708,464,714,457,716,455,709">
<area href="#andorra" alt="" shape="poly" coords="282,806,283,811,281,816,271,812,273,806">
<!-- etc... -->
</map>Obrazek ten jest wielkości naszej mapy i w całości przeźroczysty. W dodatku musi posiadać najwyższy indeks – tak, by zdefiniowane obszary były klikalne z użyciem natywnych elementów MAP i AREA.
Dodajemy JavaScript i zbieramy układankę w całość
Kiedy już stworzyliśmy pliki graficzne, odpowiednio je pocięliśmy, stworzyliśmy kod HTML i właściwie go wypozycjonowaliśmy z pomocą CSS, nadszedł czas na połączenie wszystkich elementów mapy w zgrabną całość. Wykorzystamy do tego JavaScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | jQuery('.map').each(function() { var map = jQuery(this); var getCountry = function(area) { return jQuery(jQuery(area).attr('href').replace(/#/, '.')); }; map.find('area').on({ 'mouseenter': function() { getCountry(this).addClass('hover'); }, 'mouseleave': function() { getCountry(this).removeClass('hover'); }, 'click': function(e) { e.preventDefault(); location.href = getCountry(this).find('a').attr('href'); } }); }); |
Co dzieje się w powyższym kodzie?
Wyszukuję wszystkie kontenery o klasie map, a w nich wszystkie elementy AREA. Do każdego z tych elementów podpinam trzy zdarzenia i wykonuję pewne operacje:
- mouseenter – kiedy kursor myszy najedzie na granicę elementu AREA, wyszukujemy element państwa i dodajemy do niego klasę hover,
- mouseleave – kiedy kursor opuści element AREA, usuwamy z elementu państwa klasę hover,
- click – kiedy użytkownik kliknie w element AREA, pobieramy adres URL dla danego państwa (adres jest pobierany z elementu danego państwa) i przechodzimy pod niego.
Dlaczego w oparciu o jQuery? Projekt, do którego używałem mapy i tak był oparty o jQuery, zatem rozsądnie było korzystać z potencjału biblioteki. Poza tym chciałem mieć wsparcie dla IE8 bez niepotrzebnego kombinowania.
Jeśli jednak chciałbyś wykorzystać mapę w projekcie, który nie używa jQuery – musisz przepisać ten kod na vanilla js, ewentualnie wykorzystując bibliotekę, której i tak używasz w swoim projekcie.
Interaktywna mapa Europy – pokaż przykład
Cały kod (gdzie zdefiniowane są obszary dla wszystkich państw z mapy) dostępny jest na GitHubie.
Wady i zalety
To już koniec tego prostego, szybkiego przykładu. Publikuję go na blogu, bowiem to raczej niecodzienne zadanie może być rozwiązane na wiele sposób, a zaprezentowane tutaj podejście wydaje się być najłatwiejszym (mimo, iż niezbyt wydajnym).
Co więcej, w sieci są już inne „gotowce” map świata z użyciem CSS, HTML i JavaScriptu, np. CSS & jQuery clickable map. To bardzo dobry przykład, w dodatku ładnie zoptymalizowany i darmowy. Niemniej jednak nie spełniał wszystkich moich założeń oraz był stosunkowo trudny w dostosowaniu, zwłaszcza jeśli chciałem mapę w kilku stanach, innych kolorach i większą! :D
Tak więc największą zaletą tego rozwiązania jest jego prostota i szybkość wykonania. W dodatku kompatybilność nawet z takimi starociami jak IE8! Największą wadą – mała wydajność i potrzeba wielu „ciężkich” grafik.
W najbliższym czasie wrócę do tematu tej mapy, choć najprawdopodobniej już tylko na GitHubie. Do zrobienia jest poprawienie wydajności, optymalizacja wielkości, dodanie wersji responsywnej i parę innych, umilających życie pierdół :-)
By the way
Kilka problemów napotkanych podczas tworzenia tej mapy:
- Z początku wszystkie obrazki państw złączyłem w jeden wielki obrazek z wykorzystaniem CSS Sprites. Efektem tego napotkałem na ciekawy problem – Chrome i Firefox nie renderuje obrazków szerszych/wyższych niż 32767 pikseli (2^15 – 1).
- Przy tworzeniu tak wielkiego obrazka Adobe Photoshop bardzo szybko wymiękł. Program ten nie potrafi bowiem eksportować (w opcji Save for Web & Devices) obrazków większych niż 6000 x 3000 pikseli. Do manipulacji nimi przydaje się ImageMagick.
- Do wytyczania granic państw/obszarów AREA użyłem świetnego narzędzia: Online Image Map Editor.
- Do łączenia półprzeźroczystych, wielkich obrazków PNG użyłem ImageMagick oraz komendy:
1convert a.png b.png +append -quality 50 c.png
Jeśli ktoś ma pomysł na poprawę wydajności lub usprawnienie mapy – zapraszam do dyskusji.
nie lepiej dać
niż
i jeszcze zaprzęgać do tego js?
Ciekawy przykład, główną zaleta jest zastosowanie tagów map/area w których możemy określić dokładny zakres kraju. Jednakże do prostszych zastosować możemy stworzyć div’y w postaci kwadratów danego kraju, np.: http://pcb-polska.pl/dystrybutorzy, gdzie wykorzystujemy tylko jeden obraz: http://pcb-polska.pl/img/bg-sellers.png
Pozdrawiam!
@andrzej:
nie da rady – hover robisz na obiektach AREA elementu MAP, które definiują konkretne obszary mapy. W JavaScript wykrywasz, że na danym obszarze masz :hover i po klasie szukasz powiązanego elementu LI, któremu dajesz klasę „hover”. To ma kilka plusów, choć i minus – brak interaktywności dla przeglądarek bez JavaScriptu. Choć funkcjonalność jest zachowana, więc nie jest źle :-)
@kicaj:
Twoje podejście jest również dobre, lecz wymaga pozycjonowania każdego państwa z osobna. Niektóre państwa mogą dodatkowo składać się z kilku wysepek w różnych częściach mapy, co dodatkowo utrudni zabawę. Niemniej jednak dobry sposób na optymalizację (kosztem wygody).
Ps. u siebie również mógłbym zastosować jeden wielki obrazek (i tak na początku zrobiłem), ale przeglądarki tego nie ogarnęły :-) przy mniejszej mapce/mniejszej ilości obszarów by przeszło.
Witam,
Dzięki za bardzo fajną lekcje. Zauważyłem jeden byczek ale nie w kodzie :) W pkt 2 zamień mouseenter na mouseleave.
@Papol:
dzięki, słuszna uwaga :) Poprawione!
Możesz dać przykład, że dwa kraje są obok siebie do zaznaczenia?
@hyh: http://beztroskiewczasy.pl/mapa/
Mały problem jest jak jest kilka panstw jak w przykładzie, nie widac całego spritea z cieniowaniem zaznaczonego kraju