Эффект реалистичного перелистывания страниц на JS
Представляю вашему вниманию — один из возможных вариантов реализации довольно забавного приема, для создания эффекта реалистичного перелистывания страниц.
Демо и документация: nodlik.github.io/StPageFlip
Github: github.com/Nodlik/StPageFlip
Подобный эффект я реализовывал данным давно, еще в университете и на Delphi. Получилось вполне достойно, правда времени я потратил тогда очень много. Сейчас — во время самоизоляции, стало интересно реализовать что-то подобное на JS, для PC и мобильных устройств.
Признаться честно, я не уверен в практической применимости данного эффекта, и думаю что в большинстве случаев — нет ничего лучше простой смены картинок без всякой анимации. Но это вполне можно использовать, допустим на сайтов ресторанов, для публикации меню (но главное — что бы рядом дублировалось ссылкой!).
Написано все на Typescript. Не использовались ни какие сторонние библиотеки. Зависимостей нет.
Ключевые особенности библиотеки
- Работает как с простыми изображениями, с отрисовкой на canvas, так и с html блоками — используя css трансформации
- Довольно гибкая система конфигурации и простое API
- Поддерживает мобильные устройства
- Автоматическая смена ориентации между портретным и ландшафтным режимом
Код писал с оглядкой только на ES6+, и модульная система тоже ES6. Поддержка браузерами в среднем на уровне 90%, основываясь на caniuse.com.
Установка
Установка возможна из npm:
npm install page-flip
Либо, скачать собранные файлы из репозитория
Базовый вариант инициализации библиотеки может быть примерно таким:
<div id="book">
<div class="my-page">
Page one
</div>
<div class="my-page">
Page two
</div>
<div class="my-page">
Page three
</div>
<div class="my-page">
Page four
</div>
</div>
import {PageFlip} from 'page-flip';
const pageFlip = new PageFlip(document.getElementById('book'),
{
width: 400, // required parameter - base page width
height: 600 // required parameter - base page height
}
);
pageFlip.loadFromHTML(document.querySelectorAll('.my-page'));
Более подробное описание, и спецификацию API — можно найти по ссылке выше.
Я же хочу рассказать о некоторых проблемах и нюансах с которыми столкнулся во время разработки.
Расчеты
Первое, о чем нужно рассказать, это математическая модель. В принципе, все расчеты довольно тривиальны, но у меня отняло немало времени.
Основная задача, которую требуется решить — это определить угол трансформации поворота перелистываемой страницы. Давай посмотрим на следующее изображение:
Точкой «A» обозначена текущая точка касания в книге. Исходя из положения этой точки — необходимо выполнить дальнейшие расчеты.
Для определения угла — необходимо рассчитать два значения — расстояние от точки А до верхней и правой границ книги. На изображении ниже они обозначены T и L соответственно.
G — диагональ угла, можно рассчитать по теореме Пифагора.
В итоге, для расчета поворота изображения можно воспользоваться следующей формулой: angle = — 2 * acos(L / G), и главное не забывать что точкой трансформации в данном случае является верхний левый угол страницы.
После расчета угла — остается самая трудоемкая часть — это расчет области видимости страницы. То, что должно быть видимо необходимо оставить, а остальное, соответственно — обрезать.
Для начала — нужно найти точки пересечения перелистываемой страницы с границами книги. На рисунками они обозначены точками B и C.
Я делал это самым простым и незамысловатым способом — в лоб. Строил уравнения прямых по двум точкам, и далее искал их точку пересечения.
Найдя все точки пересечения, определяем вершины области видимости — и по этим точкам уже выполняем обрезку перелистываемой страницы.
В принципе, вся математика здесь и сводится к двум вещам:
— расчет угла трансформации
— расчет области видимости страниц
Наложение теней производится уже на основе ранее сделанных расчетов.
Теперь перейдем к некоторым моментам, с которыми пришлось столкнутся при реализации.
Общий алгоритм довольно простой и сводится к поворотам и обрезке страниц.
В случае с canvas и простыми изображениями — все довольно просто. После выполнения расчетов, используются методы 2d контекста холста, такие как translate, rotate и clip.
С html блоками несколько сложнее. И если с поворотом — благодаря css трансформациям, проблем нет, то с обрезкой все оказалось несколько хуже.
В итоге, самым простым способом, оказалось использование свойства clip-path и css фигуры — polygon. Но прежде чем задавать вершины многоугольника для обрезки, необходимо выполнить трансформацию координат точек из «глобальных» холста — в локальные, относительно html элемента. Решается это обратным применением матрицы поворота, со сдвигом относительно позиции элемента.
Другой проблемой было масштабирование и авто-позиционирование книги. Это я попытался решить объектом конфигурации, который передается при создании. Но в итоге, параметров стало довольно много, и получилось не совсем удобно и не очевидно.
Для сборки сначала использовал Webpack, но в итоге все-таки решил попробовать rollup.js, и был очень приятно удивлен итоговым кодом. Webpack пока остается, поскольку справляется со сборкой на лету в несколько раз быстрее, а при разработке это удобнее.
Буду рад услышать комментарии, и предложения по дальнейшей разработке библиотеки.