Знакомство с новым API CSS Houdini для рисования

Знакомство с новым API CSS Houdini для рисования

В мире веб-разработки постоянно появляются новые инструменты и технологии, призванные упростить работу разработчиков и расширить возможности создания интерактивных и визуально привлекательных веб-страниц. Одним из таких инновационных инструментов является CSS Houdini — набор низкоуровневых API, позволяющих разработчикам получить доступ к внутренним механизмам CSS. В этой статье мы подробно рассмотрим один из ключевых компонентов CSS Houdini — API для рисования (Painting API).

Что такое CSS Houdini?

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

Основные компоненты CSS Houdini включают:

  • Painting API
  • Layout API
  • Animation Worklet
  • Typed OM
  • Properties and Values API
  • Parser API

В этой статье мы сосредоточимся на Painting API, который позволяет создавать пользовательские фоны, границы и другие визуальные элементы с помощью JavaScript.

Основы API для рисования CSS Houdini

API для рисования CSS Houdini предоставляет разработчикам возможность программно создавать изображения, которые могут использоваться в качестве значений для свойств CSS, таких как background-image, border-image и других. Это позволяет создавать сложные визуальные эффекты, которые ранее были невозможны или требовали использования внешних изображений.

Как работает API для рисования?

API для рисования работает путем определения класса PaintWorklet, который содержит метод paint(). Этот метод вызывается браузером каждый раз, когда необходимо отрисовать элемент. Внутри метода paint() разработчики могут использовать объект CanvasRenderingContext2D для рисования, аналогично тому, как это делается на HTML5 canvas.

Преимущества использования API для рисования CSS Houdini

Использование API для рисования CSS Houdini предоставляет ряд преимуществ:

  • Повышенная производительность по сравнению с использованием JavaScript для манипуляций с DOM
  • Возможность создания сложных визуальных эффектов без использования внешних изображений
  • Улучшенная интеграция с CSS и возможность использования пользовательских свойств CSS
  • Более предсказуемое поведение кросс-браузерной совместимости

Примеры использования API для рисования CSS Houdini

Рассмотрим несколько примеров использования API для рисования CSS Houdini:

1. Создание пользовательского фона

Вот простой пример создания пользовательского фона с помощью API для рисования:

 class CheckerboardPainter { paint(ctx, geom, properties) { const colors = ['#000000', '#ffffff']; const size = 32; for (let y = 0; y < geom.height / size; y++) { for (let x = 0; x < geom.width / size; x++) { const color = colors[(x + y) % colors.length]; ctx.fillStyle = color; ctx.fillRect(x * size, y * size, size, size); } } } } registerPaint('checkerboard', CheckerboardPainter); 

Этот код создает шахматный узор, который можно использовать в качестве фона элемента:

 .element { background-image: paint(checkerboard); } 

2. Создание пользовательской границы

API для рисования также можно использовать для создания пользовательских границ:

 class RainbowBorderPainter { paint(ctx, geom, properties) { const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']; const borderWidth = 5; colors.forEach((color, index) => { ctx.strokeStyle = color; ctx.lineWidth = borderWidth; ctx.strokeRect( borderWidth * index, borderWidth * index, geom.width - borderWidth * index * 2, geom.height - borderWidth * index * 2 ); }); } } registerPaint('rainbow-border', RainbowBorderPainter); 

Этот код создает радужную границу, которую можно применить к элементу:

 .element { border-image: paint(rainbow-border); } 

Поддержка браузерами

На момент написания статьи поддержка API для рисования CSS Houdini в браузерах выглядит следующим образом:

Браузер Версия Поддержка
Chrome 65+ Полная
Firefox - В разработке
Safari - В разработке
Edge (Chromium) 79+ Полная

Как видно из таблицы, на данный момент полная поддержка API для рисования CSS Houdini доступна только в браузерах на основе Chromium. Однако ожидается, что в будущем поддержка будет расширена и на другие браузеры.

Применение API для рисования в реальных проектах

Несмотря на ограниченную поддержку браузерами, API для рисования CSS Houdini уже сейчас может быть использован в реальных проектах. Рассмотрим несколько сценариев применения:

1. Создание сложных градиентов

API для рисования позволяет создавать градиенты, которые невозможно реализовать с помощью стандартных средств CSS. Например, можно создать градиент с несколькими цветовыми остановками и нелинейным переходом между ними:

 class ComplexGradientPainter { paint(ctx, geom, properties) { const gradient = ctx.createLinearGradient(0, 0, geom.width, geom.height); gradient.addColorStop(0, 'red'); gradient.addColorStop(0.2, 'orange'); gradient.addColorStop(0.4, 'yellow'); gradient.addColorStop(0.6, 'green'); gradient.addColorStop(0.8, 'blue'); gradient.addColorStop(1, 'purple'); ctx.fillStyle = gradient; ctx.fillRect(0, 0, geom.width, geom.height); } } registerPaint('complex-gradient', ComplexGradientPainter); 

Этот градиент можно применить к элементу следующим образом:

 .element { background-image: paint(complex-gradient); } 

2. Динамические паттерны

API для рисования позволяет создавать динамические паттерны, которые могут изменяться в зависимости от размера элемента или пользовательских свойств CSS:

 class DynamicPatternPainter { static get inputProperties() { return ['--pattern-size', '--pattern-color']; } paint(ctx, geom, properties) { const size = parseInt(properties.get('--pattern-size').toString()) || 20; const color = properties.get('--pattern-color').toString() || 'black'; ctx.fillStyle = color; for (let y = 0; y < geom.height; y += size) { for (let x = 0; x < geom.width; x += size) { ctx.beginPath(); ctx.arc(x, y, size / 2, 0, 2 * Math.PI); ctx.fill(); } } } } registerPaint('dynamic-pattern', DynamicPatternPainter); 

Этот паттерн можно применить и настроить следующим образом:

 .element { background-image: paint(dynamic-pattern); --pattern-size: 30px; --pattern-color: blue; } 

3. Интерактивные элементы интерфейса

API для рисования можно использовать для создания интерактивных элементов интерфейса, таких как кнопки с эффектом при наведении:

 class HoverEffectPainter { static get inputProperties() { return ['--hover-progress']; } paint(ctx, geom, properties) { const progress = properties.get('--hover-progress').toString(); const radius = Math.min(geom.width, geom.height) / 2 * progress; ctx.fillStyle = 'rgba(0, 0, 255, 0.2)'; ctx.beginPath(); ctx.arc(geom.width / 2, geom.height / 2, radius, 0, 2 * Math.PI); ctx.fill(); } } registerPaint('hover-effect', HoverEffectPainter); 

Этот эффект можно применить к кнопке следующим образом:

 .button { background-image: paint(hover-effect); --hover-progress: 0; transition: --hover-progress 0.3s; } .button:hover { --hover-progress: 1; } 

Ограничения API для рисования CSS Houdini

Несмотря на мощные возможности, API для рисования CSS Houdini имеет некоторые ограничения:

  • Ограниченная поддержка браузерами на данный момент
  • Невозможность использования внешних ресурсов (изображений, шрифтов) внутри PaintWorklet
  • Отсутствие доступа к DOM и другим API браузера внутри PaintWorklet
  • Ограниченные возможности анимации (хотя это может быть решено с помощью CSS-анимаций и пользовательских свойств)
Читайте также  Оптимальный способ передачи данных между компонентами в React.

Сравнение API для рисования CSS Houdini с другими технологиями

Давайте сравним API для рисования CSS Houdini с другими технологиями, которые могут использоваться для создания визуальных эффектов на веб-страницах:

Технология Преимущества Недостатки
CSS Houdini Painting API
  • Высокая производительность
  • Тесная интеграция с CSS
  • Программируемость
  • Ограниченная поддержка браузерами
  • Сложность в освоении
SVG
  • Широкая поддержка браузерами
  • Масштабируемость
  • Анимация
  • Может быть медленнее для сложных изображений
  • Сложность создания динамических эффектов
Canvas
  • Высокая производительность
  • Полный контроль над рисованием
  • Не интегрируется с CSS
  • Растровый вывод
CSS-градиенты и фильтры
  • Простота использования
  • Широкая поддержка браузерами
  • Ограниченные возможности
  • Сложность создания комплексных эффектов

Как видно из сравнения, API для рисования CSS Houdini предоставляет уникальное сочетание производительности, программируемости и интеграции с CSS, что делает его мощным инструментом для создания сложных визуальных эффектов.

Лучшие практики использования API для рисования CSS Houdini

При работе с API для рисования CSS Houdini следует придерживаться следующих лучших практик:

  • Использовать прогрессивное улучшение: применять API для рисования как дополнительный слой функциональности, сохраняя базовую функциональность для браузеров без поддержки
  • Оптимизировать производительность: минимизировать количество перерисовок и сложность рисования
  • Использовать пользовательские свойства CSS для параметризации рисования
  • Тестировать в различных браузерах и на различных устройствах
  • Использовать полифилы для обеспечения совместимости со старыми браузерами

Будущее API для рисования CSS Houdini

API для рисования CSS Houdini находится в постоянном развитии. В будущем можно ожидать следующих улучшений и дополнений:

  • Расширение поддержки браузерами
  • Добавление новых возможностей, таких как поддержка 3D-рендеринга
  • Улучшение интеграции с другими API CSS Houdini
  • Оптимизация производительности

Примеры сложных эффектов с использованием API для рисования

Рассмотрим несколько примеров сложных эффектов, которые можно создать с помощью API для рисования CSS Houdini:

1. Анимированный фон "Волны"

 class WavesPainter { static get inputProperties() { return ['--wave-count', '--wave-color']; } paint(ctx, geom, properties) { const waveCount = properties.get('--wave-count').toString() || 3; const waveColor = properties.get('--wave-color').toString() || 'blue'; ctx.fillStyle = waveColor; const waveHeight = geom.height / waveCount; const amplitude = waveHeight / 4; for (let i = 0; i < waveCount; i++) { ctx.beginPath(); ctx.moveTo(0, (i + 1) * waveHeight); for (let x = 0; x < geom.width; x++) { const y = Math.sin(x / 50 + i * Math.PI / 2) * amplitude + (i + 1) * waveHeight; ctx.lineTo(x, y); } ctx.lineTo(geom.width, geom.height); ctx.lineTo(0, geom.height); ctx.closePath(); ctx.fill(); } } } registerPaint('waves', WavesPainter); 

Этот эффект создает анимированный фон с волнами. Его можно применить следующим образом:

 .waves-background { background-image: paint(waves); --wave-count: 5; --wave-color: rgba(0, 0, 255, 0.2); animation: wave-animation 5s linear infinite; } @keyframes wave-animation { 0% { --wave-offset: 0; } 100% { --wave-offset: 100; } } 

2. Кастомный прогресс-бар

 class ProgressBarPainter { static get inputProperties() { return ['--progress', '--bar-color', '--background-color']; } paint(ctx, geom, properties) { const progress = properties.get('--progress').toString() || 0; const barColor = properties.get('--bar-color').toString() || 'green'; const backgroundColor = properties.get('--background-color').toString() || 'lightgray'; // Рисуем фон ctx.fillStyle = backgroundColor; ctx.fillRect(0, 0, geom.width, geom.height); // Рисуем прогресс ctx.fillStyle = barColor; ctx.fillRect(0, 0, geom.width * progress / 100, geom.height); // Добавляем текстуру ctx.fillStyle = 'rgba(255, 255, 255, 0.1)'; for (let i = 0; i < geom.width; i += 10) { ctx.fillRect(i, 0, 5, geom.height); } } } registerPaint('progress-bar', ProgressBarPainter); 

Этот код создает кастомный прогресс-бар с текстурой. Его можно использовать так:

 .progress-bar { width: 300px; height: 20px; background-image: paint(progress-bar); --progress: 75; --bar-color: #4CAF50; --background-color: #f0f0f0; } 

3. Интерактивный фон "Созвездие"

 class ConstellationPainter { static get inputProperties() { return ['--star-count', '--line-color', '--star-color']; } paint(ctx, geom, properties) { const starCount = parseInt(properties.get('--star-count').toString()) || 50; const lineColor = properties.get('--line-color').toString() || 'rgba(255, 255, 255, 0.2)'; const starColor = properties.get('--star-color').toString() || 'white'; // Создаем звезды const stars = Array.from({length: starCount}, () => ({ x: Math.random() * geom.width, y: Math.random() * geom.height, radius: Math.random() * 2 + 1 })); // Рисуем линии между звездами ctx.strokeStyle = lineColor; ctx.beginPath(); for (let i = 0; i < stars.length; i++) { for (let j = i + 1; j < stars.length; j++) { const distance = Math.hypot(stars[i].x - stars[j].x, stars[i].y - stars[j].y); if (distance < 100) { ctx.moveTo(stars[i].x, stars[i].y); ctx.lineTo(stars[j].x, stars[j].y); } } } ctx.stroke(); // Рисуем звезды ctx.fillStyle = starColor; stars.forEach(star => { ctx.beginPath(); ctx.arc(star.x, star.y, star.radius, 0, 2 * Math.PI); ctx.fill(); }); } } registerPaint('constellation', ConstellationPainter); 

Этот эффект создает интерактивный фон "Созвездие". Его можно применить так:

 .constellation-background { background-image: paint(constellation); --star-count: 100; --line-color: rgba(255, 255, 255, 0.2); --star-color: white; background-color: #000033; } 

Интеграция API для рисования с другими технологиями

API для рисования CSS Houdini может эффективно интегрироваться с другими веб-технологиями для создания еще более мощных и интерактивных эффектов:

1. Интеграция с JavaScript

JavaScript может использоваться для динамического изменения пользовательских свойств CSS, которые влияют на рисование:

 const element = document.querySelector('.animated-element'); let progress = 0; function animate() { progress = (progress + 1) % 100; element.style.setProperty('--animation-progress', progress); requestAnimationFrame(animate); } animate(); 

2. Интеграция с CSS-анимациями

CSS-анимации могут использоваться для изменения пользовательских свойств, влияющих на рисование:

 @keyframes rotate { 0% { --rotation: 0deg; } 100% { --rotation: 360deg; } } .rotating-element { background-image: paint(rotator); animation: rotate 5s linear infinite; } 

3. Интеграция с Web Animations API

Web Animations API предоставляет более гибкий контроль над анимациями:

 const element = document.querySelector('.animated-element'); element.animate([ { '--scale': '1' }, { '--scale': '1.5' }, { '--scale': '1' } ], { duration: 2000, iterations: Infinity }); 

Оптимизация производительности при использовании API для рисования

При работе с API для рисования CSS Houdini важно учитывать производительность. Вот несколько советов по оптимизации:

  • Минимизируйте количество перерисовок, используя CSS-анимации вместо постоянного обновления через JavaScript
  • Используйте простые геометрические фигуры и алгоритмы рисования, где это возможно
  • Ограничивайте количество элементов с пользовательской отрисовкой на странице
  • Используйте инструменты разработчика в браузере для профилирования производительности
  • Рассмотрите возможность использования Web Workers для сложных вычислений

Проблемы совместимости и их решения

Несмотря на то, что API для рисования CSS Houdini еще не поддерживается всеми браузерами, существуют способы обеспечить совместимость:

1. Использование полифилов

Существуют полифилы, которые эмулируют функциональность API для рисования в браузерах без нативной поддержки. Например, CSS Paint Polyfill:

 <script src="https://unpkg.com/css-paint-polyfill"></script> 

2. Прогрессивное улучшение

Используйте API для рисования как дополнительный слой функциональности, сохраняя базовую функциональность для браузеров без поддержки:

 .element { background-color: blue; /* Fallback для браузеров без поддержки */ background-image: paint(my-custom-background); } 

3. Определение поддержки

Используйте JavaScript для определения поддержки API для рисования и применения альтернативных стилей при необходимости:

 if ('paintWorklet' in CSS) { CSS.paintWorklet.addModule('my-paint-worklet.js'); } else { // Применить альтернативные стили } 

Заключение

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

По мере расширения поддержки браузерами и дальнейшего развития спецификации, API для рисования CSS Houdini, вероятно, станет неотъемлемой частью инструментария современного веб-разработчика. Он предоставляет уникальное сочетание производительности, гибкости и интеграции с CSS, что делает его мощным инструментом для создания инновационных и интерактивных веб-интерфейсов.

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

Дополнительные ресурсы

Для тех, кто хочет глубже изучить API для рисования CSS Houdini, рекомендуются следующие ресурсы:

  • Официальная документация W3C по CSS Houdini
  • CSS Houdini Experiments - сборник примеров и экспериментов
  • Is Houdini Ready Yet? - сайт для отслеживания поддержки браузерами
  • MDN Web Docs: CSS Painting API - подробная документация и примеры
  • Google Developers: CSS Houdini - обзор и руководства по использованию

Практические примеры использования API для рисования в реальных проектах

Рассмотрим несколько примеров того, как API для рисования CSS Houdini может быть использован в реальных веб-проектах:

1. Интерактивный фон для сайта погоды

Создадим динамический фон, который меняется в зависимости от текущей погоды:

 class WeatherBackgroundPainter { static get inputProperties() { return ['--weather-type', '--temperature']; } paint(ctx, geom, properties) { const weatherType = properties.get('--weather-type').toString(); const temperature = parseInt(properties.get('--temperature').toString()); // Базовый градиент неба const skyGradient = ctx.createLinearGradient(0, 0, 0, geom.height); skyGradient.addColorStop(0, '#87CEEB'); skyGradient.addColorStop(1, '#E0F6FF'); ctx.fillStyle = skyGradient; ctx.fillRect(0, 0, geom.width, geom.height); // Рисуем элементы в зависимости от погоды switch(weatherType) { case 'sunny': this.drawSun(ctx, geom, temperature); break; case 'rainy': this.drawRain(ctx, geom); break; case 'snowy': this.drawSnow(ctx, geom); break; } } drawSun(ctx, geom, temperature) { ctx.fillStyle = 'yellow'; ctx.beginPath(); ctx.arc(geom.width * 0.8, geom.height * 0.2, 50, 0, 2 * Math.PI); ctx.fill(); // Измените цвет неба в зависимости от температуры const alpha = Math.min((temperature - 20) / 20, 1); ctx.fillStyle = `rgba(255, 165, 0, ${alpha})`; ctx.fillRect(0, 0, geom.width, geom.height); } drawRain(ctx, geom) { ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)'; for (let i = 0; i < 100; i++) { const x = Math.random() * geom.width; const y = Math.random() * geom.height; ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + 10, y + 20); ctx.stroke(); } } drawSnow(ctx, geom) { ctx.fillStyle = 'white'; for (let i = 0; i < 100; i++) { const x = Math.random() * geom.width; const y = Math.random() * geom.height; ctx.beginPath(); ctx.arc(x, y, 2, 0, 2 * Math.PI); ctx.fill(); } } } registerPaint('weather-background', WeatherBackgroundPainter); 

Этот фон может быть применен к элементу следующим образом:

 .weather-display { background-image: paint(weather-background); --weather-type: 'sunny'; --temperature: 25; } 

2. Кастомный индикатор загрузки

Создадим уникальный индикатор загрузки, который может быть легко настроен с помощью CSS-переменных:

 class LoaderPainter { static get inputProperties() { return ['--progress', '--color', '--speed']; } paint(ctx, geom, properties) { const progress = parseFloat(properties.get('--progress').toString()) || 0; const color = properties.get('--color').toString() || 'blue'; const speed = parseInt(properties.get('--speed').toString()) || 1; const centerX = geom.width / 2; const centerY = geom.height / 2; const radius = Math.min(centerX, centerY) - 10; // Фоновый круг ctx.strokeStyle = 'lightgray'; ctx.lineWidth = 10; ctx.beginPath(); ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI); ctx.stroke(); // Прогресс ctx.strokeStyle = color; ctx.lineWidth = 10; ctx.beginPath(); ctx.arc(centerX, centerY, radius, -Math.PI / 2, (progress / 100) * 2 * Math.PI - Math.PI / 2); ctx.stroke(); // Анимированная точка const angle = (progress / 100) * 2 * Math.PI - Math.PI / 2; const dotX = centerX + radius * Math.cos(angle); const dotY = centerY + radius * Math.sin(angle); const currentTime = new Date().getTime() / 1000; const dotSize = 5 + 3 * Math.sin(currentTime * speed); ctx.fillStyle = color; ctx.beginPath(); ctx.arc(dotX, dotY, dotSize, 0, 2 * Math.PI); ctx.fill(); } } registerPaint('custom-loader', LoaderPainter); 

Этот индикатор загрузки может быть применен следующим образом:

 .loader { width: 200px; height: 200px; background-image: paint(custom-loader); --progress: 75; --color: #4CAF50; --speed: 2; } 

3. Интерактивная карта тепла

Создадим интерактивную карту тепла, которая реагирует на движение мыши:

 class HeatmapPainter { static get inputProperties() { return ['--mouse-x', '--mouse-y']; } paint(ctx, geom, properties) { const mouseX = parseFloat(properties.get('--mouse-x').toString()) || 0; const mouseY = parseFloat(properties.get('--mouse-y').toString()) || 0; const gradient = ctx.createRadialGradient(mouseX, mouseY, 0, mouseX, mouseY, 200); gradient.addColorStop(0, 'rgba(255, 0, 0, 0.5)'); gradient.addColorStop(1, 'rgba(255, 0, 0, 0)'); ctx.fillStyle = gradient; ctx.fillRect(0, 0, geom.width, geom.height); } } registerPaint('heatmap', HeatmapPainter); 

Для использования этой карты тепла потребуется JavaScript для отслеживания позиции мыши:

 const heatmapElement = document.querySelector('.heatmap'); heatmapElement.addEventListener('mousemove', (e) => { const rect = heatmapElement.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; heatmapElement.style.setProperty('--mouse-x', x); heatmapElement.style.setProperty('--mouse-y', y); }); 

И CSS для применения эффекта:

 .heatmap { width: 100%; height: 400px; background-image: paint(heatmap); --mouse-x: 0; --mouse-y: 0; } 

Перспективы развития API для рисования CSS Houdini

API для рисования CSS Houdini находится в активной стадии развития, и в будущем можно ожидать появления новых возможностей и улучшений:

  • Поддержка 3D-контекста рендеринга, что позволит создавать более сложные трехмерные эффекты
  • Улучшенная интеграция с другими API CSS Houdini, такими как Layout API и Animation Worklet
  • Оптимизация производительности, особенно для сложных анимаций
  • Расширение возможностей для работы с текстом и шрифтами
  • Улучшение инструментов разработки для отладки и профилирования пользовательских эффектов рисования

Заключение

API для рисования CSS Houdini представляет собой мощный инструмент, открывающий новые возможности для веб-дизайна и разработки. Он позволяет создавать сложные визуальные эффекты, которые ранее были невозможны или требовали использования сторонних библиотек и изображений. Несмотря на текущие ограничения в поддержке браузерами, этот API уже сейчас может быть эффективно использован в реальных проектах с применением прогрессивного улучшения.

По мере расширения поддержки браузерами и дальнейшего развития спецификации, API для рисования CSS Houdini, вероятно, станет неотъемлемой частью инструментария современного веб-разработчика. Он предоставляет уникальное сочетание производительности, гибкости и интеграции с CSS, что делает его мощным инструментом для создания инновационных и интерактивных веб-интерфейсов.

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

В конечном итоге, API для рисования CSS Houdini - это шаг в будущее веб-разработки, где границы между дизайном и программированием становятся все более размытыми, а возможности для творчества и инноваций - практически безграничными.

Читайте также  Инструкция по созданию эффекта пульсации кнопок в стиле Material Design
Советы по созданию сайтов