Эффект реалистичного перелистывания страниц на JS

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

dl_ozc-qvg-odenna9cutcw9f38.jpeg

Демо и документация: 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 — можно найти по ссылке выше.
Я же хочу рассказать о некоторых проблемах и нюансах с которыми столкнулся во время разработки.

Расчеты


Первое, о чем нужно рассказать, это математическая модель. В принципе, все расчеты довольно тривиальны, но у меня отняло немало времени.
Основная задача, которую требуется решить — это определить угол трансформации поворота перелистываемой страницы. Давай посмотрим на следующее изображение:
bbt5u1xl5ubzrwvhykk8_hvzjdc.png

Точкой «A» обозначена текущая точка касания в книге. Исходя из положения этой точки — необходимо выполнить дальнейшие расчеты.
Для определения угла — необходимо рассчитать два значения — расстояние от точки А до верхней и правой границ книги. На изображении ниже они обозначены T и L соответственно.
vnpqu73x5gvoiing6x0nwmb6uxg.png
G — диагональ угла, можно рассчитать по теореме Пифагора.
В итоге, для расчета поворота изображения можно воспользоваться следующей формулой: angle = — 2 * acos(L / G), и главное не забывать что точкой трансформации в данном случае является верхний левый угол страницы.
После расчета угла — остается самая трудоемкая часть — это расчет области видимости страницы. То, что должно быть видимо необходимо оставить, а остальное, соответственно — обрезать.
Для начала — нужно найти точки пересечения перелистываемой страницы с границами книги. На рисунками они обозначены точками B и C.
5v406-trmhzb6ojcg2pt-adimli.png

Я делал это самым простым и незамысловатым способом — в лоб. Строил уравнения прямых по двум точкам, и далее искал их точку пересечения.
Найдя все точки пересечения, определяем вершины области видимости — и по этим точкам уже выполняем обрезку перелистываемой страницы.
tlimlfrs-_wa5hdfsjcuzonqujo.png
В принципе, вся математика здесь и сводится к двум вещам:
— расчет угла трансформации
— расчет области видимости страниц
Наложение теней производится уже на основе ранее сделанных расчетов.

Теперь перейдем к некоторым моментам, с которыми пришлось столкнутся при реализации.

Общий алгоритм довольно простой и сводится к поворотам и обрезке страниц.
В случае с canvas и простыми изображениями — все довольно просто. После выполнения расчетов, используются методы 2d контекста холста, такие как translate, rotate и clip.
С html блоками несколько сложнее. И если с поворотом — благодаря css трансформациям, проблем нет, то с обрезкой все оказалось несколько хуже.
В итоге, самым простым способом, оказалось использование свойства clip-path и css фигуры — polygon. Но прежде чем задавать вершины многоугольника для обрезки, необходимо выполнить трансформацию координат точек из «глобальных» холста — в локальные, относительно html элемента. Решается это обратным применением матрицы поворота, со сдвигом относительно позиции элемента.
Другой проблемой было масштабирование и авто-позиционирование книги. Это я попытался решить объектом конфигурации, который передается при создании. Но в итоге, параметров стало довольно много, и получилось не совсем удобно и не очевидно.
Для сборки сначала использовал Webpack, но в итоге все-таки решил попробовать rollup.js, и был очень приятно удивлен итоговым кодом. Webpack пока остается, поскольку справляется со сборкой на лету в несколько раз быстрее, а при разработке это удобнее.

Буду рад услышать комментарии, и предложения по дальнейшей разработке библиотеки.

© Habrahabr.ru