You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/translations/ru/3-terrarium/3-intro-to-DOM-and-closures/README.md

22 KiB

Проект "Террариум", часть 3: Манипуляция DOM и замыкание

DOM и замыкание

Скетчноут от Tomomi Imura

Викторина перед лекцией

Викторина перед лекцией

Введение

Манипуляция DOM, или "Document Object Model" (Модель Объекта Документа), является ключевым аспектом веб-разработки. Согласно MDN, "Document Object Model (DOM) — это представление данных объектов, составляющих структуру и содержание документа в вебе". Сложности, связанные с манипуляцией DOM, часто становились причиной использования JavaScript-фреймворков вместо чистого JavaScript для управления DOM, но мы справимся самостоятельно!

Кроме того, в этом уроке будет введено понятие замыкания в JavaScript, которое можно представить как функцию, заключённую внутри другой функции, так что внутренняя функция имеет доступ к области видимости внешней функции.

Замыкания в JavaScript — это обширная и сложная тема. В этом уроке рассматривается самая базовая идея: в коде террариума вы найдете замыкание — внутреннюю функцию и внешнюю функцию, сконструированные таким образом, чтобы внутренняя функция имела доступ к области видимости внешней функции. Для получения более подробной информации о том, как это работает, посетите обширную документацию.

Мы будем использовать замыкание для манипуляции DOM.

Представьте DOM как дерево, которое отображает все способы, которыми можно манипулировать документом веб-страницы. Были разработаны различные API (интерфейсы прикладного программирования), чтобы программисты могли использовать выбранный язык программирования для доступа к DOM и его редактирования, изменения, перестановки и управления.

Представление дерева DOM

Представление DOM и HTML-разметки, которая ссылается на него. Автор Olfa Nasraoui

В этом уроке мы завершим наш интерактивный проект террариума, создав JavaScript, который позволит пользователю манипулировать растениями на странице.

Предварительные требования

У вас должны быть готовы HTML и CSS для вашего террариума. К концу этого урока вы сможете перемещать растения внутрь и наружу террариума, перетаскивая их.

Задача

В папке террариума создайте новый файл с именем script.js. Импортируйте этот файл в секцию <head>:

	<script src="./script.js" defer></script>

Примечание: используйте defer при импорте внешнего JavaScript-файла в HTML, чтобы JavaScript выполнялся только после полной загрузки HTML-файла. Вы также можете использовать атрибут async, который позволяет скрипту выполняться во время парсинга HTML-файла, но в нашем случае важно, чтобы HTML-элементы были полностью доступны для перетаскивания перед выполнением скрипта.


Элементы DOM

Первое, что вам нужно сделать, — это создать ссылки на элементы, которые вы хотите манипулировать в DOM. В нашем случае это 14 растений, которые сейчас находятся в боковых панелях.

Задача

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'));

Что здесь происходит? Вы обращаетесь к документу и ищете в его DOM элемент с определённым идентификатором. Помните, что в первом уроке по HTML вы присвоили каждому изображению растения уникальный идентификатор (id="plant1")? Теперь вы воспользуетесь этим. После идентификации каждого элемента вы передаете этот элемент в функцию dragElement, которую вы скоро создадите. Таким образом, элемент в HTML становится доступным для перетаскивания, или скоро станет.

Почему мы ссылаемся на элементы по идентификатору? Почему не по их CSS-классу? Вы можете обратиться к предыдущему уроку по CSS, чтобы ответить на этот вопрос.


Замыкание

Теперь вы готовы создать замыкание dragElement, которое является внешней функцией, заключающей внутреннюю функцию или функции (в нашем случае их будет три).

Замыкания полезны, когда одна или несколько функций должны иметь доступ к области видимости внешней функции. Вот пример:

function displayCandy(){
	let candy = ['jellybeans'];
	function addCandy(candyType) {
		candy.push(candyType)
	}
	addCandy('gumdrops');
}
displayCandy();
console.log(candy)

В этом примере функция displayCandy окружает функцию, которая добавляет новый тип конфет в массив, который уже существует в функции. Если вы выполните этот код, массив candy будет неопределённым, так как он является локальной переменной (локальной для замыкания).

Как сделать массив candy доступным? Попробуйте переместить его за пределы замыкания. Таким образом, массив станет глобальным, а не останется доступным только в локальной области видимости замыкания.

Задача

Под объявлениями элементов в script.js создайте функцию:

function dragElement(terrariumElement) {
	//set 4 positions for positioning on the screen
	let pos1 = 0,
		pos2 = 0,
		pos3 = 0,
		pos4 = 0;
	terrariumElement.onpointerdown = pointerDrag;
}

dragElement получает свой объект terrariumElement из объявлений в начале скрипта. Затем вы устанавливаете некоторые локальные позиции на 0 для объекта, переданного в функцию. Это локальные переменные, которые будут изменяться для каждого элемента, когда вы добавите функциональность перетаскивания в замыкание для каждого элемента. Террариум будет заполнен этими перетаскиваемыми элементами, поэтому приложению нужно отслеживать, где они размещены.

Кроме того, элемент террариума, переданный в эту функцию, получает событие pointerdown, которое является частью веб-API, предназначенных для управления DOM. onpointerdown срабатывает, когда нажата кнопка или, в нашем случае, когда выбран перетаскиваемый элемент. Этот обработчик событий работает как в веб-браузерах, так и в мобильных, с некоторыми исключениями.

Обработчик событий onclick имеет гораздо большую поддержку в разных браузерах; почему бы не использовать его здесь? Подумайте о точном типе взаимодействия с экраном, который вы пытаетесь создать.


Функция Pointerdrag

Элемент terrariumElement готов к перетаскиванию; когда событие onpointerdown срабатывает, вызывается функция pointerDrag. Добавьте эту функцию сразу под строкой: terrariumElement.onpointerdown = pointerDrag;:

Задача

function pointerDrag(e) {
	e.preventDefault();
	console.log(e);
	pos3 = e.clientX;
	pos4 = e.clientY;
}

Происходит несколько вещей. Во-первых, вы предотвращаете выполнение стандартных событий, которые обычно происходят при pointerdown, используя e.preventDefault();. Таким образом, у вас больше контроля над поведением интерфейса.

Вернитесь к этой строке, когда полностью создадите файл скрипта, и попробуйте убрать e.preventDefault() — что произойдет?

Во-вторых, откройте index.html в окне браузера и исследуйте интерфейс. Когда вы нажимаете на растение, вы можете увидеть, как событие 'e' захватывается. Изучите событие, чтобы увидеть, сколько информации собирается одним событием pointerdown!

Далее обратите внимание, как локальные переменные pos3 и pos4 устанавливаются в e.clientX. Вы можете найти значения e в панели инспекции. Эти значения фиксируют координаты x и y растения в момент, когда вы нажимаете на него или касаетесь его. Вам потребуется тонкий контроль над поведением растений при их перемещении, поэтому вы отслеживаете их координаты.

Становится ли более понятно, почему всё приложение построено на одном большом замыкании? Если бы его не было, как бы вы поддерживали область видимости для каждого из 14 перетаскиваемых растений?

Завершите начальную функцию, добавив ещё две манипуляции событий pointer под pos4 = e.clientY:

document.onpointermove = elementDrag;
document.onpointerup = stopElementDrag;

Теперь вы указываете, что хотите, чтобы растение перемещалось вместе с указателем при его перемещении, и чтобы жест перетаскивания прекращался, когда вы снимаете выделение с растения. onpointermove и onpointerup являются частью того же API, что и onpointerdown. Интерфейс будет выдавать ошибки сейчас, так как вы ещё не определили функции elementDrag и stopElementDrag, поэтому создайте их далее.

Функции elementDrag и stopElementDrag

Вы завершите своё замыкание, добавив ещё две внутренние функции, которые будут управлять тем, что происходит при перетаскивании растения и прекращении его перетаскивания. Поведение, которое вы хотите, заключается в том, чтобы вы могли перетаскивать любое растение в любое время и размещать его где угодно на экране. Этот интерфейс довольно нейтральный (например, нет зоны сброса), чтобы вы могли настроить свой террариум так, как вам нравится, добавляя, удаляя и перемещая растения.

Задача

Добавьте функцию elementDrag сразу после закрывающей фигурной скобки функции pointerDrag:

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';
}

В этой функции вы много редактируете начальные позиции 1-4, которые вы установили как локальные переменные во внешней функции. Что здесь происходит?

При перетаскивании вы переназначаете pos1, делая его равным pos3 (который вы ранее установили как e.clientX) минус текущее значение e.clientX. Вы выполняете аналогичную операцию для pos2. Затем вы сбрасываете pos3 и pos4 на новые координаты X и Y элемента. Вы можете наблюдать эти изменения в консоли при перетаскивании. Затем вы изменяете стиль CSS растения, чтобы установить его новую позицию на основе новых позиций pos1 и pos2, вычисляя верхние и левые координаты X и Y растения, сравнивая его смещение с этими новыми позициями.

offsetTop и offsetLeft — это CSS-свойства, которые устанавливают позицию элемента относительно его родителя; его родителем может быть любой элемент, который не имеет позиционирования static.

Всё это пересчитывание позиций позволяет вам точно настроить поведение террариума и его растений.

Задача

Последняя задача для завершения интерфейса — добавить функцию stopElementDrag после закрывающей фигурной скобки функции elementDrag:

function stopElementDrag() {
	document.onpointerup = null;
	document.onpointermove = null;
}

Эта небольшая функция сбрасывает события onpointerup и onpointermove, чтобы вы могли либо перезапустить перемещение растения, начав его перетаскивание снова, либо начать перетаскивание нового растения.

Что произойдет, если вы не установите эти события в null?

Теперь ваш проект завершён!

🥇Поздравляем! Вы завершили свой красивый террариум. завершённый террариум


🚀Челлендж

Добавьте новый обработчик событий в своё замыкание, чтобы сделать что-то ещё с растениями; например, двойной щелчок по растению, чтобы переместить его на передний план. Проявите креативность!

Викторина после лекции

Викторина после лекции

Обзор и самостоятельное изучение

Хотя перемещение элементов по экрану кажется тривиальным, существует множество способов сделать это и множество подводных камней, в зависимости от желаемого эффекта. На самом деле существует целый API для перетаскивания и сброса, который вы можете попробовать. Мы не использовали его в этом модуле, так как эффект, который мы хотели, был немного другим, но попробуйте этот API в своём собственном проекте и посмотрите, чего вы сможете добиться.

Найдите больше информации о событиях указателя в документации W3C и на MDN web docs.

Всегда проверяйте возможности браузеров, используя CanIUse.com.

Задание

Поработайте немного больше с DOM


Отказ от ответственности:
Этот документ был переведен с помощью сервиса автоматического перевода Co-op Translator. Хотя мы стремимся к точности, пожалуйста, учитывайте, что автоматические переводы могут содержать ошибки или неточности. Оригинальный документ на его родном языке следует считать авторитетным источником. Для получения критически важной информации рекомендуется профессиональный перевод человеком. Мы не несем ответственности за любые недоразумения или неправильные интерпретации, возникшие в результате использования данного перевода.