Привет друг ! В этой статье попробуем разобраться как сделать скриншот 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
Логи показывать не буду, там и так всё понятно, а вот результат покажу😇:
Спасибо за внимание! До Новых Встреч!🤗🤗🤗