Цифровой элемент
15 минут на чтение
1780
Отправь статью на почту?

Работа с динамическим контентом

Подписаться

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

Содержание

Примеры ситуаций динамического контента в коде

  1. Страница товара, пользователь нажимает на кнопку "Купить", появляется модальное окно со множеством информации о текущем статусе корзины. Разметка этого модального окна изначально на странице отсутствует, чтобы страница загружалась быстрее. После нажатия пользователем на кнопку скрипт выполняет асинхронный запрос к серверу, который, в свою очередь, предоставит всю необходимую информацию о статусе корзины для вывода ее в модальном окне.
  2. Страница каталога, изначально отображено 20 карточек товаров. Пользователь пролистывает страницу вниз, подгружаются еще 20 новых элементов. При дальнейшей прокрутке подгрузятся еще +20 товаров и так далее. Если бы на странице каталога сразу отображались сотни карточек товаров, то страница грузилась бы значительно дольше, чем страница с двадцатью элементами.

Пример общей структуры динамического контента в коде:

<body>
<!--	Будет пустым во время загрузки страницы-->
<div id="container-for-rendering"></div>
<!-- В bundle.js происходит асинхронный запрос, запрашивающий информацию при определенном действии пользователя -->
<script src="bundle.js"></script>
</body> 

Особенности работы с динамическим контентом на примере классовых компонентов

При загрузке страницы скрипт ищет все соответствующие селектору DOM-элементы на странице и привязывает к ним определенный функционал:

<body>
	<div id="static">
		<label>
			<input type="text" placeholder="+7 (999) 999-99-99" data-js-mask-input>
		</label>
	
		<label>
			<input type="text" placeholder="+7 (999) 999-99-99" data-js-mask-input>
		</label>
	
		<label>
			<input type="text" placeholder="+7 (999) 999-99-99" data-js-mask-input>
		</label>
	</div>
	
	<script src="bundle.js"></script>
</body> 
 import IMask from 'imask'

export default class Masks {
  els = {
    instance: '[data-js-mask-input]'
  }
  
  constructor() {
    this.init()
  }
  
  init() {
    document.querySelectorAll(this.els.instance).forEach((el) => {
      new IMask(el)
    })
  }
}

Если изначально на странице отсутствуют DOM-элементы, к которым происходит обращение в скрипте, то после их динамического появления на странице необходимо запустить скрипт повторно для того, чтобы функционал компонента заработал:

 <!-- Будет пустым во время загрузки страницы -->
<div id="container-for-rendering">
 <!-- Подгрузится асинхронно --> 
<label> 
<input type="text" placeholder="+7 (999) 999-99-99" data-js-mask-input> 
</label> 
</div>

Общий алгоритм работы с динамическим контентом:

  1. Запросить динамическое содержимое посредством ajax/fetch;
  2. Вставить загруженный контент в необходимое место;
  3. Вызвать CustomEvent - всплывающее событие, сигнализирующее о том, что на странице появился новый контент и содержащее в себе его HTML nodes.
  4. Подхватить событие и вызвать реинициализацию метода класса.

Запрос и вставка динамического содержимого

export default class MyClass {
  els = {
    containerForRendering: '#container-for-rendering'
  }

  // ...

  render(markup) {
    document.querySelector(this.els.containerForRendering).insertAdjacentHTML('afterend', markup)
  }

  async getMarkup(url, cfg) {
    const resp = await fetch(url, cfg)
    return (resp.ok) ?
      await (cfg.type === 'json') ?
        resp.json() : resp.text() :
        Promise.reject(resp.statusText)
  }
  
  updateMarkup() {
    this.getMarkup.then((markup) => {
      this.render(markup)
    })
  }
}

Асинхронный запрос вернёт разметку, которая затем добавится в конец элемента для динамического контента.

Работа с CustomEvents

const el = document.querySelector('#my-element')
const event = new Event('myCustomEventName')

// Подписываемся на событие
el.addEventListener('myCustomEventName', (e) => {
  // ...
})

// Вызываем событие
el.dispatchEvent(event)

Помимо стандартных событий в JavaScript можно создавать свои события, подписывать на них элементы и вызывать их при необходимости.

Кастомные события с дополнительными данными

const el = document.querySelector('#my-element')

// Подписываемся на событие
el.addEventListener('myCustomEventName', (e) => {
  // ...
})

// Создаем событие с данными
const customEvent = new CustomEvent('myCustomEventName', {
  detail: {} // любые данные
})

// Вызываем событие
el.dispatchEvent(customEvent)

Любое кастомное событие можно дополнить абсолютно любыми данными, которые могут пригодиться после дальнейшего “отлова” события.

Функция для уведомления о появлении нового контента

// ./js/generic/evented.js

export const dispatchContentLoaded = (data) => {
  document.dispatchEvent(
    new CustomEvent('contentLoaded', {
      detail: data
    })
  )
}

Работа с событиями появления нового контента

import {dispatchContentLoaded} from './generic/eventing'

export default class MyClass {
  els = {
    containerForRendering: '#container-for-rendering'
  }

  // ...

  render(string) {
    const dynamicNode = document.querySelector(this.els.containerForRendering)
    
    dynamicNode.insertAdjacentHTML('afterend', string)
    dispatchContentLoaded({
      content: dynamicNode
    })
  }
}

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

Функция подписки на обновление (на примере классового компонента)

export class AccordionCollection {
  // Коллекция экземпляров
  _collection = []

  constructor() {
    super(instance, Accordion)
    this.init()
    this.bindEvents()
  }

  // Ищет внутри коллекции по DOM-элементу. У экземпляров класса должен быть параметр instance, по нему идет проверка
  getByDOMElement(DOMElement) {
    return this.collection.find(item => item.instance === DOMElement)
  }

  // Добавляет экземпляр в коллекцию. По-умолчанию проверяет, существует ли экземпляр с таким instance.
  set collection(newCollectionItem) {
    const itemInCollection = this.getByDOMElement(newCollectionItem.instance)
    if (!itemInCollection) {
      this._collection = [...this._collection, newCollectionItem]
    }
  }

  // Публичная коллекция
  get collection() {
    return this._collection
  }

  init(context = document) {
    context.querySelectorAll(instance).forEach((el) => {
      this.collection = new Accordion(el)
    })
  }

  bindEvents() {
    onContentLoaded((e) => { // слушаем обновление контента
      this.init(e.detail.content) // e.detail.content - обновленное содержимое (html nodes)
    })
  }
}

В конструкторе класса, помимо инициализации (вызова метода init), происходит привязка события отслеживания появления динамического контента. И при появлении новых элементов произойдет повторная инициализация компонента с контекстом e.detail.content (поиск новых DOM-элементов произойдет в контексте конкретного элемента, полученного в e.detail.content).

Работа с экземплярами класса Accordion происходит через так называемую коллекцию. Коллекцией в данном контексте является совокупность компонентов, неоднократно повторяющихся на странице. В рамках коллекции можно реализовать различные вспомогательные методы для более гибкого управления экземплярами класса, например, метод получения доступа к конкретным инстансам экземпляра коллекции.

Обработка событий при статическом контенте

export default class MyClass {
  els = {
    something: '[data-js-my-clickable-element]' 
  }

  constructor() {
    this.bindEvents()
  }
  
  handleClick(e) {
    // ...
  }
  
  bindEvents() {
    document.querySelectorAll(this.els.something).forEach((node) => {
      node.addEventListener('click', (e) => this.handleClick(e))
    })    
  }
}

Обработка событий при динамическом контенте

export default class MyClass {
  els = {
    something: '[data-js-my-clickable-element]'
  }

  constructor() {
    this.bindEvents()
  }

  handleClick(e) {
    if (e.target.matches(this.els.something) || e.target.closest(this.els.something)) {
      // обработка клика
    }
  }

  bindEvents() {
    document.addEventListener('click', (e) => this.handleClick(e))
  }
}

Подход к обработке событий статического и динамического контента немного отличается.

Для статического достаточно привязать событие к уже имеющимся на момент загрузки страницы DOM-элементам.

Для динамического контента такой подход не сработает, так как к новым элементам привязки событий уже не будет. Поэтому необходимо добавить обработчик события на элемент document (корневой элемент, то есть буквально на всю страницу), затем “отловить” цель события через e.target.matches (или через e.target.closest, что, правда, чуть менее эффективно, но в некоторых случаях необходимо) и далее обработать событие.

Заключение

Работа с динамическим контентом меняет парадигму мышления разработчика, ведь нельзя рассчитывать на то, что при запуске страницы будут все необходимые элементы для работы конкретного функционала компонента, ибо даже небольшая задержка до появления контента после асинхронного запроса “поломает” неподготовленный к подобному поведению компонент. В лучшем случае - не заработает конкретный функционал, привязанный к элементу. В худшем - “положит” намертво всю страницу целиком.

Поэтому при разработке стоит заранее продумать логику работы с контентом и определить, возможен ли вариант динамического появления элементов или же они всегда будут находиться на странице при ее загрузке.

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


Мне не нравится
Россия, Челябинская область, Челябинск, ул. Энтузиастов, 2, оф. 200 Телефон: +7 (351) 220-45-35

Блог

TОП-40 плагинов Figma
TОП-40 плагинов Figma

Для создания дизайнов и прототипов большинство современных дизайнеров использует Figma. Его основное преимущество перед другими графическими прогр...

11.мар.2021
134712
Микроразметка schema.org
Микроразметка schema.org

Schema.org – микроразметка, позволяющая структурировать данные на сайте для поисковых систем. С ее помощью поисковые системы понимают, какие данные...

28.мая.2019
124124
Как попасть на Яндекс.Карты, Google.Карты, 2GIS
Как попасть на Яндекс.Карты, Google.Карты, 2GIS

Если ваша компания ведет бизнес офлайн, размещение на Яндекс.Картах и Google и 2GIS поможет рассказать об этом потенциальным клиентам. Присутствие...

17.июл.2019
104260
404 ошибка – страница не найдена
404 ошибка – страница не найдена

404 ошибка (страница не найдена) – это ответ сервера, который возникает, когда сервер не может отобразить запрашиваемую страницу по указанному адре...

13.авг.2019
47259
Семантика сайта
Семантика сайта

Семантическое ядро – это набор фраз, соответствующих поисковым запросам пользователей в поисковых системах, которые характеризуют определенную тема...

21.окт.2019
22742
Сброс кеша DNS в Google Chrome
Сброс кеша DNS в Google Chrome

Для сброса кеша DNS в Google Chrome: Введите в адресной строке браузера chrome://net-internals/#dns и нажмите кнопку Clear host cache; Зат...

31.янв.2020
17756
Как составить ТЗ на разработку сайта
Как составить ТЗ на разработку сайта

ТЗ (техническое задание) – очень полезный документ, в котором описаны все разделы сайта, все элементы страницы и функциональность всех модулей. Пол...

14.мая.2021
17619
Микроразметка Open Graph
Микроразметка Open Graph

Open Graph – стандарт микроразметки, который позволяет формировать превью сайта при публикации в социальных сетях. Стандарт Open Graph был р...

05.ноя.2019
16944
Как предоставить гостевой доступ к Яндекс Директ и Google Adwords
Как предоставить гостевой доступ к Яндекс Директ и Google Adwords

Контекстная реклама — один из самых простых и быстрых способов увеличения посещений веб-сайта. Особенностью этого инструмента является понятность р...

19.янв.2021
15734
Топ 10 лучших сервисов для онлайн-опросов
Топ 10 лучших сервисов для онлайн-опросов

Как провести онлайн-опрос аудитории легко и не затратить на составление анкет уйму времени? Ответ прост – используйте готовые формы, предлагаемые ...

28.июл.2022
14336