# Projekt Terrarium Część 3: Manipulacja DOM i Domknięcie  > Sketchnotka autorstwa [Tomomi Imura](https://twitter.com/girlie_mac) ## Quiz przed wykładem [Quiz przed wykładem](https://ff-quizzes.netlify.app/web/quiz/19) ### Wprowadzenie Manipulacja DOM, czyli "Modelu Obiektowego Dokumentu", to kluczowy aspekt tworzenia stron internetowych. Według [MDN](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction), "Model Obiektowy Dokumentu (DOM) to reprezentacja danych obiektów, które tworzą strukturę i treść dokumentu w sieci." Wyzwania związane z manipulacją DOM w sieci często były powodem korzystania z frameworków JavaScript zamiast czystego JavaScriptu do zarządzania DOM, ale my poradzimy sobie sami! Dodatkowo, w tej lekcji wprowadzimy pojęcie [domknięcia w JavaScript](https://developer.mozilla.org/docs/Web/JavaScript/Closures), które można sobie wyobrazić jako funkcję zamkniętą w innej funkcji, dzięki czemu funkcja wewnętrzna ma dostęp do zakresu funkcji zewnętrznej. > Domknięcia w JavaScript to obszerny i złożony temat. W tej lekcji poruszymy najbardziej podstawową ideę, że w kodzie tego terrarium znajdziesz domknięcie: funkcję wewnętrzną i funkcję zewnętrzną skonstruowane w taki sposób, aby funkcja wewnętrzna miała dostęp do zakresu funkcji zewnętrznej. Aby dowiedzieć się więcej o tym, jak to działa, odwiedź [obszerną dokumentację](https://developer.mozilla.org/docs/Web/JavaScript/Closures). Użyjemy domknięcia do manipulacji DOM. Pomyśl o DOM jako o drzewie, które reprezentuje wszystkie sposoby, w jakie dokument strony internetowej może być manipulowany. Stworzono różne API (Interfejsy Programowania Aplikacji), aby programiści, używając swojego ulubionego języka programowania, mogli uzyskać dostęp do DOM i edytować, zmieniać, przestawiać oraz zarządzać nim w inny sposób.  > Reprezentacja DOM i odpowiadającego mu kodu HTML. Źródło: [Olfa Nasraoui](https://www.researchgate.net/publication/221417012_Profile-Based_Focused_Crawler_for_Social_Media-Sharing_Websites) W tej lekcji ukończymy nasz interaktywny projekt terrarium, tworząc kod JavaScript, który pozwoli użytkownikowi manipulować roślinami na stronie. ### Wymagania wstępne Powinieneś mieć już gotowy kod HTML i CSS dla swojego terrarium. Po zakończeniu tej lekcji będziesz mógł przenosić rośliny do terrarium i z niego, przeciągając je. ### Zadanie W folderze terrarium utwórz nowy plik o nazwie `script.js`. Zaimportuj ten plik w sekcji `
`: ```html ``` > Uwaga: użyj `defer`, importując zewnętrzny plik JavaScript do pliku HTML, aby JavaScript został wykonany dopiero po pełnym załadowaniu pliku HTML. Możesz również użyć atrybutu `async`, który pozwala na wykonanie skryptu podczas parsowania pliku HTML, ale w naszym przypadku ważne jest, aby elementy HTML były w pełni dostępne do przeciągania przed uruchomieniem skryptu przeciągania. --- ## Elementy DOM Pierwszą rzeczą, którą musisz zrobić, jest utworzenie referencji do elementów, które chcesz manipulować w DOM. W naszym przypadku są to 14 roślin obecnie czekających w paskach bocznych. ### Zadanie ```html dragElement(document.getElementById('plant1')); dragElement(document.getElementById('plant2')); dragElement(document.getElementById('plant3')); dragElement(document.getElementById('plant4')); dragElement(document.getElementById('plant5')); dragElement(document.getElementById('plant6')); dragElement(document.getElementById('plant7')); dragElement(document.getElementById('plant8')); dragElement(document.getElementById('plant9')); dragElement(document.getElementById('plant10')); dragElement(document.getElementById('plant11')); dragElement(document.getElementById('plant12')); dragElement(document.getElementById('plant13')); dragElement(document.getElementById('plant14')); ``` Co tu się dzieje? Odwołujesz się do dokumentu i przeszukujesz jego DOM, aby znaleźć element o określonym Id. Pamiętasz, że w pierwszej lekcji o HTML nadałeś indywidualne Id każdemu obrazkowi rośliny (`id="plant1"`)? Teraz wykorzystasz ten wysiłek. Po zidentyfikowaniu każdego elementu przekazujesz ten element do funkcji o nazwie `dragElement`, którą za chwilę stworzysz. W ten sposób element w HTML staje się gotowy do przeciągania lub wkrótce będzie. ✅ Dlaczego odwołujemy się do elementów za pomocą Id? Dlaczego nie za pomocą ich klasy CSS? Możesz odwołać się do poprzedniej lekcji o CSS, aby odpowiedzieć na to pytanie. --- ## Domknięcie Teraz jesteś gotowy, aby utworzyć domknięcie `dragElement`, które jest funkcją zewnętrzną otaczającą funkcję lub funkcje wewnętrzne (w naszym przypadku będą trzy). Domknięcia są przydatne, gdy jedna lub więcej funkcji musi mieć dostęp do zakresu funkcji zewnętrznej. Oto przykład: ```javascript function displayCandy(){ let candy = ['jellybeans']; function addCandy(candyType) { candy.push(candyType) } addCandy('gumdrops'); } displayCandy(); console.log(candy) ``` W tym przykładzie funkcja `displayCandy` otacza funkcję, która dodaje nowy typ cukierka do tablicy, która już istnieje w funkcji. Jeśli uruchomisz ten kod, tablica `candy` będzie niezdefiniowana, ponieważ jest to zmienna lokalna (lokalna dla domknięcia). ✅ Jak możesz sprawić, aby tablica `candy` była dostępna? Spróbuj przenieść ją poza domknięcie. W ten sposób tablica stanie się globalna, zamiast pozostawać dostępna tylko w lokalnym zakresie domknięcia. ### Zadanie Pod deklaracjami elementów w `script.js` utwórz funkcję: ```javascript function dragElement(terrariumElement) { //set 4 positions for positioning on the screen let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; terrariumElement.onpointerdown = pointerDrag; } ``` `dragElement` pobiera swój obiekt `terrariumElement` z deklaracji na początku skryptu. Następnie ustawiasz kilka lokalnych pozycji na `0` dla obiektu przekazanego do funkcji. Są to zmienne lokalne, które będą manipulowane dla każdego elementu, gdy dodasz funkcjonalność przeciągania i upuszczania w ramach domknięcia do każdego elementu. Terrarium będzie wypełniane przez te przeciągane elementy, więc aplikacja musi śledzić, gdzie są umieszczane. Dodatkowo, element `terrariumElement`, który jest przekazywany do tej funkcji, jest przypisany do zdarzenia `pointerdown`, które jest częścią [web APIs](https://developer.mozilla.org/docs/Web/API) zaprojektowanych do zarządzania DOM. `onpointerdown` uruchamia się, gdy przycisk jest wciśnięty lub, w naszym przypadku, gdy dotknięty zostanie element przeciągalny. Ten obsługiwacz zdarzeń działa zarówno w [przeglądarkach internetowych, jak i mobilnych](https://caniuse.com/?search=onpointerdown), z kilkoma wyjątkami. ✅ [Obsługiwacz zdarzeń `onclick`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onclick) ma znacznie większe wsparcie w różnych przeglądarkach; dlaczego nie użyć go tutaj? Pomyśl o dokładnym typie interakcji ekranowej, którą próbujesz tutaj stworzyć. --- ## Funkcja Pointerdrag Element `terrariumElement` jest gotowy do przeciągania; gdy zdarzenie `onpointerdown` zostanie uruchomione, wywoływana jest funkcja `pointerDrag`. Dodaj tę funkcję zaraz pod tą linią: `terrariumElement.onpointerdown = pointerDrag;`: ### Zadanie ```javascript function pointerDrag(e) { e.preventDefault(); console.log(e); pos3 = e.clientX; pos4 = e.clientY; } ``` Dzieje się tu kilka rzeczy. Po pierwsze, zapobiegasz domyślnym zdarzeniom, które normalnie występują przy `pointerdown`, używając `e.preventDefault();`. W ten sposób masz większą kontrolę nad zachowaniem interfejsu. > Wróć do tej linii, gdy całkowicie zbudujesz plik skryptu i spróbuj bez `e.preventDefault()` - co się stanie? Po drugie, otwórz `index.html` w oknie przeglądarki i sprawdź interfejs. Gdy klikniesz roślinę, możesz zobaczyć, jak zdarzenie 'e' jest przechwytywane. Zbadaj zdarzenie, aby zobaczyć, ile informacji jest zbieranych przez jedno zdarzenie pointerdown! Następnie zauważ, jak lokalne zmienne `pos3` i `pos4` są ustawiane na e.clientX. Możesz znaleźć wartości `e` w panelu inspekcji. Te wartości przechwytują współrzędne x i y rośliny w momencie, gdy ją klikniesz lub dotkniesz. Będziesz potrzebować precyzyjnej kontroli nad zachowaniem roślin, gdy je klikniesz i przeciągniesz, więc śledzisz ich współrzędne. ✅ Czy staje się bardziej jasne, dlaczego cała ta aplikacja jest zbudowana jako jedno duże domknięcie? Gdyby tak nie było, jak utrzymałbyś zakres dla każdej z 14 przeciągalnych roślin? Uzupełnij początkową funkcję, dodając jeszcze dwie manipulacje zdarzeniami wskaźnika pod `pos4 = e.clientY`: ```html document.onpointermove = elementDrag; document.onpointerup = stopElementDrag; ``` Teraz wskazujesz, że chcesz, aby roślina była przeciągana razem ze wskaźnikiem, gdy go przesuwasz, oraz aby gest przeciągania zakończył się, gdy odznaczysz roślinę. `onpointermove` i `onpointerup` są częścią tego samego API co `onpointerdown`. Interfejs będzie teraz wyrzucał błędy, ponieważ nie zdefiniowałeś jeszcze funkcji `elementDrag` i `stopElementDrag`, więc zbuduj je teraz. ## Funkcje elementDrag i stopElementDrag Uzupełnisz swoje domknięcie, dodając dwie kolejne funkcje wewnętrzne, które będą obsługiwać to, co dzieje się, gdy przeciągasz roślinę i przestajesz ją przeciągać. Chcesz, aby można było przeciągać dowolną roślinę w dowolnym momencie i umieszczać ją w dowolnym miejscu na ekranie. Ten interfejs jest dość nieograniczony (nie ma na przykład strefy upuszczania), aby umożliwić Ci zaprojektowanie swojego terrarium dokładnie tak, jak chcesz, dodając, usuwając i przemieszczając rośliny. ### Zadanie Dodaj funkcję `elementDrag` zaraz po zamykającym nawiasie klamrowym `pointerDrag`: ```javascript function elementDrag(e) { pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; console.log(pos1, pos2, pos3, pos4); terrariumElement.style.top = terrariumElement.offsetTop - pos2 + 'px'; terrariumElement.style.left = terrariumElement.offsetLeft - pos1 + 'px'; } ``` W tej funkcji dokonujesz wielu edycji początkowych pozycji 1-4, które ustawiłeś jako zmienne lokalne w funkcji zewnętrznej. Co tu się dzieje? Podczas przeciągania ponownie przypisujesz `pos1`, ustawiając go jako równy `pos3` (który wcześniej ustawiłeś jako `e.clientX`) minus bieżącą wartość `e.clientX`. Podobną operację wykonujesz dla `pos2`. Następnie resetujesz `pos3` i `pos4` do nowych współrzędnych X i Y elementu. Możesz obserwować te zmiany w konsoli podczas przeciągania. Następnie manipulujesz stylem CSS rośliny, aby ustawić jej nową pozycję na podstawie nowych pozycji `pos1` i `pos2`, obliczając współrzędne X i Y rośliny na podstawie porównania jej przesunięcia z tymi nowymi pozycjami. > `offsetTop` i `offsetLeft` to właściwości CSS, które ustawiają pozycję elementu na podstawie jego rodzica; rodzicem może być dowolny element, który nie jest ustawiony jako `static`. Całe to przeliczanie pozycji pozwala Ci precyzyjnie dostosować zachowanie terrarium i jego roślin. ### Zadanie Ostatnim zadaniem, aby ukończyć interfejs, jest dodanie funkcji `stopElementDrag` po zamykającym nawiasie klamrowym `elementDrag`: ```javascript function stopElementDrag() { document.onpointerup = null; document.onpointermove = null; } ``` Ta mała funkcja resetuje zdarzenia `onpointerup` i `onpointermove`, abyś mógł albo wznowić przesuwanie rośliny, zaczynając ją przeciągać ponownie, albo zacząć przeciągać nową roślinę. ✅ Co się stanie, jeśli nie ustawisz tych zdarzeń na null? Teraz ukończyłeś swój projekt! 🥇Gratulacje! Ukończyłeś swoje piękne terrarium.  --- ## 🚀Wyzwanie Dodaj nowy obsługiwacz zdarzeń do swojego domknięcia, aby zrobić coś więcej z roślinami; na przykład, podwójne kliknięcie rośliny, aby przenieść ją na przód. Bądź kreatywny! ## Quiz po wykładzie [Quiz po wykładzie](https://ff-quizzes.netlify.app/web/quiz/20) ## Przegląd i samodzielna nauka Chociaż przeciąganie elementów po ekranie wydaje się trywialne, istnieje wiele sposobów na to i wiele pułapek, w zależności od efektu, który chcesz osiągnąć. W rzeczywistości istnieje całe [API przeciągania i upuszczania](https://developer.mozilla.org/docs/Web/API/HTML_Drag_and_Drop_API), które możesz wypróbować. Nie użyliśmy go w tym module, ponieważ efekt, który chcieliśmy osiągnąć, był nieco inny, ale spróbuj tego API w swoim własnym projekcie i zobacz, co możesz osiągnąć. Znajdź więcej informacji o zdarzeniach wskaźnika w [dokumentacji W3C](https://www.w3.org/TR/pointerevents1/) oraz w [dokumentacji MDN](https://developer.mozilla.org/docs/Web/API/Pointer_events). Zawsze sprawdzaj możliwości przeglądarek, korzystając z [CanIUse.com](https://caniuse.com/). ## Zadanie [Pracuj trochę więcej z DOM](assignment.md) --- **Zastrzeżenie**: Ten dokument został przetłumaczony za pomocą usługi tłumaczenia AI [Co-op Translator](https://github.com/Azure/co-op-translator). Chociaż dokładamy wszelkich starań, aby zapewnić poprawność tłumaczenia, prosimy pamiętać, że automatyczne tłumaczenia mogą zawierać błędy lub nieścisłości. Oryginalny dokument w jego rodzimym języku powinien być uznawany za źródło autorytatywne. W przypadku informacji o kluczowym znaczeniu zaleca się skorzystanie z profesjonalnego tłumaczenia przez człowieka. Nie ponosimy odpowiedzialności za jakiekolwiek nieporozumienia lub błędne interpretacje wynikające z użycia tego tłumaczenia.