Найти в Дзене
IlyaDev

Делаем скриншот DOM дерева. React.

Привет друг ! В этой статье попробуем разобраться как сделать скриншот DOM дерева в React приложении.
Минута саморекламы😇: Хочешь порядка в коде, тебе сюда https://dzen.ru/a/Z1iL1J62BiwFEYgd. Ну а теперь, поехали!🛼 Как болванку буду использовать собранный проект при помощи vite: import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
function App() {
const [count, setCount] = useState(0)
return (
<>
<div>
<a href="https://vite.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code
Оглавление

Привет друг ! В этой статье попробуем разобраться как сделать скриншот DOM дерева в React приложении.


Минута саморекламы😇:

Хочешь порядка в коде, тебе сюда https://dzen.ru/a/Z1iL1J62BiwFEYgd.

Ну а теперь, поехали!🛼

1. Создаём болванку

Как болванку буду использовать собранный проект при помощи vite:

import { useState } from 'react'
import reactLogo from './assets/react.svg'
import
viteLogo from '/vite.svg'
import './App.css'

function App() {
const [count, setCount] = useState(0)

return (
<>
<div>
<a href="https://vite.dev" target="_blank">
<img src={
viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
)
}

export default App

2. Установка библиотеки

Для начала предлагаю установить библиотеку html2canvas:

pnpm i html2canvas

Есть прикольный бонус с библиотекой react-best-gradient-color-picker, если использовал эту либу на проекте для работы с градиентами, там как зависимость будет html2canvas - подгон, что тут скажешь🤩.

Вроде всё готово, для создание скриншота. Погнали фоткать)

3. Пишем логику поведения

Создадим состояние для хранения того самого скриншота, забегая вперёд оно нам не сильно то и пригодиться, ну как вариант можешь разместить его где нибудь на страннице. Напишем тип для подобного случая, желательно переиспользуемый. Ты спросишь зачем, а что б все увидели, как я умею)

Ну ты понял )
Ну ты понял )

type Nullable<T> = T | null;
const [screenDOM, setScreenDOM] = useState<Nullable<string>>(null);

Выполнение скриншота привяжем к изменению счётчика, операция асинхронная - асинхронная, значит пишем его величество useEffect:

useEffect(() => {
const element =
document.querySelector('#screenId');
if (element instanceof
HTMLElement) {
html2canvas(element).then((canvas) => {
const screenshot = canvas.toDataURL('image/png');
setScreenDOM(screenshot);
console.log('Снятый скриншот:', screenshot);
});
}
}, [count]);

Ну а теперь по пунктам, что тут происходит:

1. const element = document.querySelector('#screenId');

  • Выполняется поиск элемента с id="screenId" в DOM.
  • Метод document.querySelector возвращает первый найденный элемент или null, если элемент не найден.

2. if (element instanceof HTMLElement) {

  • Проверяется, является ли найденный элемент экземпляром HTMLElement.
  • Это важно, так как querySelector может вернуть null, а проверка гарантирует, что с element можно безопасно работать как с HTML-элементом.

3. html2canvas(element).then((canvas) => {

  • Библиотека html2canvas используется для создания снимка (скриншота) элемента.
  • Возвращается Promise, который разрешается, когда скриншот готов. Результат — это объект HTMLCanvasElement.

4. const screenshot = canvas.toDataURL('image/png');

  • Получение данных скриншота в формате Data URL. Это строка, закодированная в Base64, представляющая изображение в формате PNG.
  • Пример строки: data:image/png;base64,....

5. setScreenDOM(screenshot);

  • Сохранение скриншота в состояние screenDOM. Теперь приложение может использовать эту строку (например, для отображения изображения).

6. console.log('Снятый скриншот:', screenshot);

  • Вывод строки screenshot в консоль для проверки.

7. }, [count]);

  • Второй параметр в useEffect — массив зависимостей.
  • Эффект запускается заново только тогда, когда значение переменной count изменяется.

Не забываем повесить id на тот элемент который желаем запечатлеть для потомков, пусть будет родительская дивка в разметке App.tsx:

<div id={'screenId'}>

Ну вот и всё 🚀🚀🚀!

4. Итоговый код

Код компоненты App.tsx:

import {useEffect, useState} from 'react'
import reactLogo from './assets/react.svg'
import
viteLogo from '/vite.svg'
import './App.css'
import html2canvas from "html2canvas";

type Nullable<T> = T | null;

function App() {
const [count, setCount] = useState(0);
const [screenDOM, setScreenDOM] = useState<Nullable<string>>(null);

useEffect(() => {
const element =
document.querySelector('#screenId');

if (element instanceof
HTMLElement) {
html2canvas(element).then((canvas) => {
const screenshot = canvas.toDataURL('image/png');
setScreenDOM(screenshot);
console.log('Снятый скриншот:', screenshot);
});
}
}, [count]);
// ну или просто в консоль выведем второй раз :)
console.log(screenDOM);

// обрати внимание на id
return (
<div id={'screenId'}>
<div>
<a href="https://vite.dev" target="_blank">
<img src={
viteLogo} className="logo" alt="Vite logo"/>
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo"/>
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</div>
)
}

export default App

Логи показывать не буду, там и так всё понятно, а вот результат покажу😇:

count === 0;
count === 0;
count === 1;
count === 1;

Спасибо за внимание! До Новых Встреч!🤗🤗🤗