[CSS] Wizualizacja ekranu meczowego Premier League za pomocą HTML i CSS
Jestem wielkim fanem piłki nożnej. Bardzo często zdarza mi się je oglądać. Jedną z lig, które oglądam jest liga angielska.
W poprzednim sezonie pojawiła się nowa wizualizacja ekranów meczowych i od momentu jak je zobaczyłem, to zapragnąłem odtworzyć podobny efekt. Dla tych, którzy nie są obeznani w transmisjach z ligi angielskiej poniżej zamieściłem nagranie z przykładowego meczu.
Jak widać, animacje polegają na przesuwaniu się paneli w odpowiednim czasie. Efekt wydaje się nieskomplikowany do zbudowania za pomocą HTML i CSS.
Struktura HTML projektu
Praktycznie każdy projekt, który prezentuję na łamach tego bloga zaczyna się od przedstawienia struktury HTML. Ma to na celu zobrazowanie ilości elementów, które okażą się przydatne w celu osiągnięcia zamierzonej animacji. W przypadku projektu z tego wpisu kod HTML wygląda następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <div class="intro"> <div class="intro__panel--left"></div> <div class="intro__panel--right"> <div class="intro__league-badge-wrapper"> <div class="intro__league-badge-outer"> <img src="./premier-league-logo.png" alt="Premier League Badge" class="intro__league-logo"> <img src="./premier-league-oneline-badge.png" alt="Premier League Badge" class="intro__league-badge"> </div> </div> </div> <div class="intro__teams-wrapper"> <div class="intro__teams"> <div class="intro__team--home"> <div class="intro__team-name"> <div class="intro__field-info">Live from Anfield</div> <div class="intro__team-name-label">Liverpool</div> </div> <div class="intro__team-badge-wrapper"> <img src="./liverpool-badge.png" alt="Liverpool FC badge" class="intro__team-badge"> </div> </div> <div class="intro__team--away"> <div class="intro__team-name">Hull City</div> <div class="intro__team-badge-wrapper"> <img src="./hull-city-badge.png" alt="Hull City AFC badge" class="intro__team-badge"> </div> </div> <div class="intro__hashtag">#LIVvHUL</div> </div> </div> </div> |
Kod HTML został podzielony na 3 sekcje: 2 panele i część w której znajdują się informacje o meczu. Analizując powyższy kod, można zobaczyć, że element o klasie .intro__field-info
został umieszczony wewnątrz elementu z klasą .intro__team-name
. Jest to celowe działanie, gdyż będziemy chcieli pozycjonować informacje o miejscu odbywania się meczu względem elementu z nazwą drużyny.
Inną rzeczą wartą uwagi jest umiejscowienie dwóch wersji loga rozgrywek. Obydwa loga zostały umieszczone obok siebie w jednym elemencie nadrzędnym. Dzięki temu, o wiele łatwiej będzie zreprodukować animację przejścia między wersjami loga rozgrywek.
Kod CSS widoku z logiem ligi
Efekt posiada dwa stany/widoki: widok z logiem rozgrywek na pełnym ekranie i widok z zespołami i informacjami o meczu. W tej części skupimy się na wyświetleniu pierwszego widoku. Logo wraz z nazwą rozgrywek będzie wyświetlone na środku panelu, który będzie wypełniał całą szerokość ekranu. Kod wygląda następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | @font-face { font-family: 'PremierSans-Regular'; src: url('./fonts/PremierLeagueW01-Regular.woff2') format('woff2'); } @font-face { font-family: 'PremierSans-Bold'; src: url('./fonts/PremierLeagueW01-Bold.woff2') format('woff2'); } @font-face { font-family: 'PremierSans-Light'; src: url('./fonts/PremierLeagueW01-Light.woff2') format('woff2'); } * { box-sizing: border-box; } body { font-family: 'PremierSans-Regular', Roboto, Helvetica, Arial, sans-serif; background: url('anfield.jpeg'); background-size: cover; margin: 0; padding: 0; } .intro { display: flex; height: 80vh; width: 100%; position: absolute; top: 50%; left: 50%; transform: translate3d(-50%, -50%, 0); overflow: hidden; } [class^="intro__panel"] { background: rgba(37, 215, 224, .8); transition: all .5s cubic-bezier(0.785, 0.135, 0.150, 0.860); } .intro__panel--right { width: 100%; } .intro__league-badge-wrapper { position: relative; height: 100%; } .intro__league-badge-outer, .intro__league-logo { position: absolute; top: 50%; left: 50%; transform: translate3d(-50%, -50%, 0); } .intro__league-badge { width: 640px; height: 200px; } .intro__league-logo { height: 108px; width: auto; opacity: 0; } .intro__league-logo, .intro__league-badge { transition: opacity .5s ease-out; } |
W powyższym kodzie warto zwrócić uwagę na kilka rzeczy. Po pierwsze, zamiast deklarować wszystkie możliwe typy fontów dla wszystkich przeglądarek, zdefiniowałem tylko jeden typ Woff2
, który jest wspierany przez najnowsze wersje przeglądarek. Docelowo, ten typ czcionki jest najbardziej optymalny w kontekście ładowania czcionek przez przeglądarkę.
Kolejną rzeczą na którą warto zwrócić uwagę jest wyświetlanie tła dla elementu <body />
. Ustawiając wartość własności background-size
na cover
sprawiamy, że tło wypełnia całą dostępną przestrzeń. Jeśli z jakiegoś powodu tło będzie większe niż kontener do którego zostanie zaimplementowane, to tło się przeskaluje zachowując proporcje. Jeśli z jakiegoś powodu proporcje kontenera nie będą się zgadzały z proporcjami tła to wtedy zostanie ono odpowiednio ucięte.
Ostatnią rzeczą wartą uwagi jest wykorzystanie transform: translate3d();
ma ono na celu przerzucenie obliczeń animacji danego elementu do procesora graficznego.
Kod CSS widoku z drużynami
Pora zająć się widokiem odpowiedzialnym za wyświetlenie drużyn i informacji o spotkaniu. Pomiędzy widokiem z nazwą rozgrywek a widokiem z drużynami nastąpi przejście w trakcie, którego stopniowo będą się pojawiać kolejne elementy ekranu. Zobaczmy jak wygląda CSS:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | .intro__teams-wrapper { opacity: 0; position: absolute; top: 50%; left: 15vw; transform: translate3d(0, -50%, 0); width: 70vw; overflow: hidden; } .intro__teams { display: flex; flex-direction: column; width: 600px; } [class^="intro__team--"] { display: flex; position: relative; overflow: hidden; } .intro__team--home { transform: translate3d(-50vw, 0, 0); z-index: 3; transition: transform .5s .5s ease-out; } .intro__team--away { transform: translate3d(10vw, -100%, 0); opacity: 0; z-index: 2; transition: transform .5s 1s ease-out, opacity .5s 1.1s ease-out; } .intro__team-name { padding: 0 30px; line-height: 90px; background: rgba(64, 4, 68, 1); color: #fff; text-transform: uppercase; font-size: 72px; z-index: 2; box-shadow: 0 0 5px 1px rgba(0, 0, 0, .5); font-family: 'PremierSans-Bold', Roboto, Helvetica, Arial, sans-serif; } .intro__team--away .intro__team-name:before { content: ''; flex: 1 1 100%; height: 0; width: calc(100% - 180px); position: absolute; left: -30px; top: 0; background: #000; box-shadow: 0 1px 5px 1px rgba(0, 0, 0, .65); } .intro__team-badge-wrapper { height: 90px; width: 90px; padding: 4px; background: #fff; display: flex; justify-content: center; box-shadow: 0 0 0 0 rgba(0, 0, 0, .65); } .intro__team-badge { max-width: 100%; width: auto; max-height: 100%; height: auto; } .intro__team-badge-wrapper { transform: translate3d(-90px, 0, 0); transition: transform .5s 1.7s ease-out; } .intro__hashtag { background: rgb(37, 215, 224); display: flex; padding: 0 30px; line-height: 24px; font-weight: 700; transform: translate3d(10vw, -100%, 0); align-self: flex-start; position: relative; z-index: 1; overflow: hidden; opacity: 0; transition: transform .5s 1.7s ease-out, opacity .5s 1.8s ease-out; } .intro__hashtag:before { content: ''; height: 0; width: 100%; box-shadow: 0 1px 5px 1px rgba(0, 0, 0, .65); flex: 1 1 100%; position: absolute; top: 0; left: 0; } .intro__field-info { background: rgba(64, 4, 68, .9); color: rgb(37, 215, 224); display: flex; padding: 0 30px; line-height: 24px; font-family: 'PremierSans-Regular', Roboto, Helvetica, Arial, sans-serif; font-weight: 700; font-size: 16px; align-self: flex-end; transform: translate3d(-155px, 100%, 0); position: relative; z-index: 2; overflow: hidden; opacity: 0; position: absolute; top: 0; right: -30px; transition: transform .5s 1.7s ease-out, opacity .5s 1.8s ease-out; } .intro__field-info:after { content: ''; height: 0px; width: 100%; box-shadow: 0 -1px 5px 1px rgba(0, 0, 0, .65); flex: 1 1 100%; position: absolute; bottom: 0; left: 0; } .intro__teams, .intro__team-badge { transition: all .5s cubic-bezier(0.785, 0.135, 0.150, 0.860); } .intro--teams { justify-content: space-between; } .intro--teams .intro__panel--left { width: 30vw; } .intro--teams .intro__panel--right { width: 15vw; } .intro--teams .intro__teams-wrapper { opacity: 1; } .intro--teams .intro__team--home { transform: translate3d(0, 0, 0); padding-top: 24px; } .intro--teams .intro__team--away { transform: translate3d(10vw, 0, 0); opacity: 1; } .intro--teams .intro__league-badge { opacity: 0; position: absolute; top: 50%; left: 50%; transform: translate3d(-50%, -50%, 0); } .intro--teams .intro__league-logo { opacity: 1; } .intro--teams .intro__team-badge-wrapper { transform: translate3d(0, 0, 0); } .intro--teams .intro__hashtag { opacity: 1; transform: translate3d(10vw, 0, 0); } .intro--teams .intro__field-info { opacity: 1; transform: translate3d(-155px, 0, 0); } |
Drugi listing kodu jest dużo dłuższy od pierwszego z tego względu, że definiuje zachowanie projektu w zależności od tego czy ekran z drużynami jest aktywny, czyli ma klasę .intro--teams
. Również i tutaj jest kilka ciekawostek związanych z CSS.
Jedną z nich jest zastosowanie uciętego cieniowania elementu. Zazwyczaj gdy stosujemy zapis własności box-shadow
podobny do tego: box-shadow: 0 0 5px 1px rgba(0, 0, 0, .5);
to sprawiamy, że cień rozlewa się wokół elementu. W przypadku efektu prezentowanego w tym artykule, interesowało mnie aby cień znajdował się w konkretnym miejscu (czyli poniżej elementu i nigdzie indziej). W celu osiągnięcia tego efektu dołożyłem własność overflow: hidden;
do selektora CSS odpowiednialnego za wybór elementu nadrzędnego w stosunku do elementu cieniowanego. W taki sposób uciąłem cień.
Inną ciekawą rzeczą, jest zastosowanie opóźnień w animacjach przejść między elementami i kolejkowanie animacji: transition: transform .5s 1.7s ease-out, opacity .5s 1.8s ease-out;
. Zdecydowałem, że przejście między dwiema wartościami dla własności transform
będzie trwało pół sekundy po odczekaniu 1.7 sekundy, a zmiana stanu przezroczystości elementu nastąpi po 1.8 sekundy i również będzie trwała pół sekundy. Jak widać kolejkowanie zmian między wartościami własności jest bardzo proste. W przypadku, gdybyśmy chcieli zmieniać wartości tej samej własności w różnym momencie czasu to wtedy musielibyśmy zastosować CSS keyframes, czyli animację klatkową.
Podsumowanie
Powyższy kod był testowany w najnowszych wersjach przeglądarek Mozilla Firefox, Google Chrome i Opera. Tam działa, tak jak to sobie zaplanowałem. W innych przeglądarkach mogą wystąpić błędy.
Budując ten efekt, starałem się odwzorować jak najwierniej ekran meczowy Premier League. Zdaję sobie sprawię, że można poprawić kilka rzeczy. Między innymi timing przejść między ekranami i timing pojawiania się kolejnych elementów na ekranie (nie byłem w stanie odwzorować w 100% tego samego przejścia, jaki występuje w oryginale). Możnaby również poprawić sposób w jaki następuje przejście między dwoma wersjami loga rozgrywek.
Mam nadzieję, że dzisiejszy tekst jest dla Ciebie inspirujący i zmotywuje Ciebie do próbowania odtwarzania rzeczy, które widujesz w TV za pomocą CSS, HTML czy JavaScript. Być może dzięki temu tekstowi zdołasz się też nauczyć czegoś nowego, nowego podejścia do kodowania.
Zapraszam do komentowania i dzielenia się uwagami dotyczącymi projektu. Pliki projektu można pobrać z repozytorium na Githubie a demo można zobaczyć w tym miejscu: demo projektu Premier League.