Wykonujemy ćwiczenie, którego celem jest zrozumienie cudzego (świetnego) kodu i w drugiej części poprawienie go, aby był jeszcze lepszy. Ugruntowanie naszej wiedzy. Do dzieła.
Ok, a zatem autor ma taki przycisk:
<button class="btn">Kliknij mnie!</button>
Teraz CSS. Najpierw reset stylów:
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
A zatem tak:
- domyślnie margin i padding dla wszystkiego zostało zresetowane do 0
- domyślny content-box zamieniono na border-box, czyli jak ustawimy gdzieś w pikselach width i height na 100px to mamy 100px, nie 100px + margin, bottom, padding
- autor zapomniał o pseudoelementach before i after, albo pominął je celowo, tym niemniej je też się często resetuje
Jedziemy dalej:
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: sans-serif;
background-color: #333;
}
Czyli tak:
- wysokość body to wysokość viewportu (viewport to jest to co widzimy, bez tego, co ukryte za scrollem, bez tego, co elementami przeglądarki i ekranu)
- font-family i background color to głupoty, znamy to
- display flex, domyślny flex direction to row, czyli main axis od lewej do prawej, cross axis od góry do dołu
- justify content operuje na main axis, dla row od lewej do prawej. wycentrowanie na tej osi
- align-items operuje na cross axis dla row od góry do dołu, wycentrowanie na tej osi
Ok, idziemy dalej:
.btn {
position: relative;
padding: 1em 2em;
color: #fff;
background-color: royalblue;
border-radius: 6px;
border: none;
cursor: pointer;
overflow: hidden;
}
.btn:focus {
outline: none;
}
Czyli tak:
- position relative żeby dziecko (efekt pulsujący) mogło mieć absolute, żeby miało rodzica jako wypozycjonowany element (non static)
- overflow hidden, aby ten nasz efekt nie rozmywał się poza element
- cursor pointer i reszta raczej nic strasznego
Ok, teraz klasa dla naszego spanu, będącego efektem:
.circle {
position: absolute;
width: 80px;
height: 80px;
background-color: #fff;
border-radius: 50%;
transform: translate(-50%, -50%) scale(0);
animation: zoom .3s;
}
Czyli tak:
- Position absolute, żeby móc pozycjonować względem wypozycjonowanego rodzica (non static) absolutnie (czyli tyle a tyle od góry, tyle a tyle od lewa)
- width i height ustawione, natomiast top i left zostawiamy javascriptowi
- transform naprawia pewien błąd (chodzi o to, aby element miał środek według koordynatów top i left a nie początek według nich) oraz robi scale 0, żeby element nie istniał i mógł rosnąć
- animation to trzeba sobie napisać
Ok, animacja:
@keyframes zoom {
to {
transform: translate(-50%, -50%) scale(2);
opacity: 0;
}
}
Proste, jedyny haczyk jest taki, że jak zmieniasz transform to musisz przepisać także to, czego nie zmieniasz, to jak operowanie na classname zamiast classlist, no takie coś, o czym trzeba pamiętać.
Ok, teraz JS:
const btn = document.querySelector('.btn')
const btnAnimation = e => {
const top = e.clientY
const left = e.clientX
// pozycja, w którą klikamy
const btnTopPosition = e.target.offsetTop
const btnLeftPosition = e.target.offsetLeft
// pozycja przycisku
const insideBtnTop = top - btnTopPosition
const insideBtnLeft = left - btnLeftPosition
const circle = document.createElement('span')
circle.classList.add('circle')
circle.style.top = insideBtnTop + 'px'
circle.style.left = insideBtnLeft + 'px'
e.target.appendChild(circle)
setTimeout(() => {
circle.remove()
}, 300);
}
btn.addEventListener('click', btnAnimation)
Ten kod uprościmy sobie w następnej lekcji, ale najpierw zrozummy, co autor robi:
- clientY i clientX to properties mouse eventa, pokazuje na ile od góry/lewa viewportu kliknęliśmy
- e.target to element, który został kliknięty
- offsetTop i offsetLeft to properties HTMLElement i oznaczają gdzie w stosunku do offsetParenta zaczyna się element
Teraz proponuję wziąć kartkę i długopis i sobie to rozrysować. Ale tak, offsetTop i offsetLeft będą jednakowe dla tego elementu. I będą pokazywały jak daleko od góry/lewa viewportu (zakładając, że to viewport jest offsetParentem, dlatego ten projekt sobie później ulepszymy) zaczyna się element.
Zaś clientX, clientY to od viewportu do miejsca kliknięcia, ale słuchamy na buttonie. Czyli możemy sobie od evt.clientX odjąć evt.target.offsetTop i mamy na ile absolutnie względem klikniętego elementu zostało kliknięte.
Znaczy, mamy wartość top dla naszego elementu. Tak samo left. Rozrysujmy to, zobaczmy sobie, pamiętajmy tylko, aby buttona nie ładować w jakiś div, bo jeszcze się mu offsetParent zmieni (a jak mamy sami rozwiązanie tego problemu to super).
Postarajmy się to zrozumieć.
Ok, nie będziemy czekać do następnej lekcji, już teraz pokażę jak to zrobić lepiej. Nie żebym hejtował autora, to było ze świetnego kursu, ale my umiemy to ulepszyć i to był taki mega pomocny przykład, który w dodatku pokaże nam raz na zawsze czym jest offsetX/offsetY i że nie musimy tego sobie sami wyliczać.
Ok, taki kod jest potrzebny:
const btn = document.querySelector('.btn')
const btnAnimation = e => {
const insideBtnTop = e.offsetY;
const insideBtnLeft = e.offsetX;
const circle = document.createElement('span')
circle.classList.add('circle')
circle.style.top = insideBtnTop + 'px'
circle.style.left = insideBtnLeft + 'px'
e.target.appendChild(circle)
setTimeout(() => {
circle.remove()
}, 300);
}
btn.addEventListener('click', btnAnimation)
To wszystko wylicza top i left naszego absolutnie wypozycjonowanego spana. I tak, operujemy tylko na properties mouse eventa, nie obchodzi nas offsetParent target elementu, gdzie ten target element się znajduje i tak dalej.
Czyli offsetY pokazuje nam na ile od góry względem danego elementu (buttona) kliknęliśmy, zaś offsetX na ile od lewa danego buttona kliknęliśmy.
Wcześniej to sobie wyliczaliśmy, biorąc clientX/Y czyli koordynaty od viewportu do miejsca kliknięcia i odejmowaliśmy od nich offsetTop i offsetLeft, czyli offset target elementu względem offsetParenta.
I jako tako mieliśmy trudniejszym sposobem i bardziej zawodnym (co jak offsetParent się zmieni) takie coś, co wyliczało nam offsetY i offsetX czyli koordytany X/Y na guziku. Gdzie guzik jest całą osią, 0,0 to jest początek guzika.
I to by było na tyle, proste.