Сегодня cложно найти пользователя, который никогда бы не сталкивался с электронными книгами в формате EPUB: он открыт, гибок и совместим практически с любыми устройствами. Но реализация воспроизведения EPUB в кроссплатформенном мобильном приложении может оказаться неожиданно сложной задачей.
Именно с такой задачей мы столкнулись в нашем недавнем проекте для ведущего украинского издательства КСД. Подробности кейса скоро можно будет рассмотреть в портфолио WEZOM: клиент обратился к нам с запросом на разработку мобильного приложения с кастомным EPUB-ридером. Требовалось не просто "сделать читалку", а обеспечить в продукте стабильность, быстродействие и комфорт для читателя, соединив все это с надежной защитой авторского контента в экосистеме КСД.
Сегодня мы расскажем, как команде удалось реализовать кастомный EPUB ридер на Flutter без написания движка с нуля, благодаря применению гибридных технологий. Это позволило существенно ускорить разработку и сэкономить огромные ресурсы, предоставив клиентам и пользователям достойный продукт.
Вызовы при создании EPUB ридера на Flutter
Издательство нуждалось в качественном мобильном приложении для Android и iOS, которое работало бы и как онлайн-магазин, и как удобный ридер для купленных там электронных книг. Ключевыми требованиями к этому решению был высококачественный UX, продуманная эстетика и надежная защита авторского контента от недобросовестного копирования.
Кроссплатформенная разработка EPUB-ридера в приложении была оптимальным вариантом, поэтому техническая команда сделала очевидный выбор в пользу фреймворка Flutter. Он имеет собственный графический движок и позволяет создавать сложные приложения на Android/iOS с единой кодовой базой, которые практически ни в чем не уступают нативным. Но гибридное Flutter-приложение с EPUB-ридером поставило перед разработчиками нетривиальные задачи.
В чем крылась проблема?
Дело в том, что EPUB, несмотря на свою открытость, остается достаточно сложным для работы форматом. Если бы речь шла только о тексте, проблем не было бы. Но EPUB – это практически целый сайт в ZIP-архиве. Он содержит HTML-файлы (разделы книги) и CSS-стили, определяющие вид книги в цифре. В архив также входят шрифты, изображения и т.д.
Flutter как таковой не имеет инструментов для работы с веб-контентом: он не может рендерить HTML и CSS. Его главная задача – отрисовывать на экране свои виджеты (кнопки, текст, контейнеры).
Имеющиеся пакеты Flutter могут преобразовывать HTML-теги в виджеты фреймворка. Для простой статьи в блоге этого могло бы хватить, но в нашем случае речь шла о целых книгах со сложной версткой, стилями и интерактивностью (как в EPUB 3). Требовалось решение, которое поддерживает сложный CSS и может выполнять JavaScript.
Чтобы реализовать замысел проекта в полном объеме, команде фактически нужно было бы написать собственный браузерный движок, который умеет:
-
Парсить структуру EPUB, то есть правильно "читать" служебные файлы, чтобы понять порядок разделов, содержание и метаданные;
-
Рендеринг HTML/CSS, чтобы корректно отобразить каждую страницу-раздел со всеми стилями и возможностями настройки;
-
Проводить пагинацию – то есть динамически разбивать "плавающий" HTML-текст на страницы, с учетом размера экрана и выбранного пользователем шрифта. Это очень нетривиальный алгоритм, работа над ним была бы наиболее сложным аспектом разработки.
Создание такого движка с нуля – это целый проект в проекте, колоссальная работа. И проводить эту работу в рамках запроса на создание приложения было неоправданно дорого и долго. Так что мы начали искать альтернативы.
Решение: гибридный подход с WebView
Если разработать собственный веб-движок в рамках проекта невозможно, нужно было найти где-то готовое решение и приспособить его под наши задачи. И такой инструмент есть – это встраиваемый компонент WebView. По сути это браузер, который можно встроить в приложение.
Полноценная интеграция WebView в Flutter дала проекту важные преимущества:
-
Отпала потребность в собственном движке для рендеринга – разработчики взяли готовый, мощный и оптимизированный движок, который уже есть в каждом смартфоне.
-
Полная поддержка веб-технологий. Ведь WebView "из коробки" понимает HTML, CSS и JavaScript, что идеально подходит для формата EPUB 3.
-
Множество готовых решений. Команда получила возможность использовать любые существующие JS-библиотеки, созданные специально для отображения книг. Именно они взяли на себя всю сложную логику пагинации внутри WebView.
Подобные решения существовали и раньше. Например, демонстрационный опенсорсный проект BookReader App использует связку epub.js, Flutter и WebView для воспроизведения EPUB в формате мобильного приложения. По схожей логике работает и Kotobee Reader – популярный "читатель" электронных книг EPUB на Android и iOS. Наша задача заключалась в том, чтобы реализовать эту логику кастомно – на высоком уровне, с учетом всех потребностей бизнеса клиента.
Сокращение времени разработки через WebView позволило команде высвободить огромные ресурсы и сконцентрироваться на других аспектах разработки, сокращая затраты проекта и приближая релиз продукта.
Применение WebView для отображения EPUB: технические тонкости
Flutter имеет базовую поддержку WebView через плагины (webview_flutter для Android и iOS). Но для более сложных сценариев, таких как передача команд между Flutter и JavaScript или работа с нативным парсингом, необходима гибридная архитектура с использованием Method Channels. Чтобы сделать WebView "послушным", важно четко определить точки входа/выхода – только так Flutter, нативный код и JS могут работать как одно целое.
Чтобы реализовать в WebView полноценный парсинг и рендеринг EPUB-файлов, необходимо использовать одну из соответствующих JS-библиотек. Кратко взглянем на самые популярные из них:
- Epub.js
Это, пожалуй, самая популярная JavaScript библиотека EPUB, созданная для браузеров. Она автоматически обрабатывает разбивку на страницы и навигацию, позволяет настраивать стили и темы для удобного чтения.
- Readium
Readium JS / Readium Web – это комплексный набор инструментов, созданный консорциумом Readium Foundation. Он фокусируется на полном соответствии стандартам EPUB, включая расширенную поддержку EPUB 3 и функции доступности для инклюзивного чтения.
- Vivliostyle
Это мощная система верстки, расширяющая возможности браузера для печатных и цифровых публикаций. Она позволяет создавать сложные макеты страниц, включая сноски, колонтитулы и постраничные элементы, что делает ее хорошим выбором для отображения EPUB/веб-публикаций.
Первой идеей команды разработчиков было обращение к самой популярной библиотеке. Однако JS библиотека Epub.js не подошла, поскольку предназначена для браузеров. Она выполняет весь парсинг EPUB-файла в JavaScript и не имеет нативной поддержки под Kotlin/Swift. Это делало Epub.js непригодной для использования в мобайле с высокими требованиями к производительности и стабильности.
Как мы реализовали парсинг EPUB?
Итак, обычный парсинг EPUB в JavaScript – это неэффективное решение. Такая обработка была бы слишком медленной, особенно для больших книг. Что же делать? Мы нашли более действенный подход: реализовать парсинг на нативной стороне с помощью специализированных библиотек Readium. Экосистема Readium Foundation предлагает для этого незаменимые инструменты:
-
readium/kotlin-toolkit для Android.
-
readium/swift-toolkit для iOS.
Эти библиотеки делают всю сложную работу: распаковывают архив, анализируют структуру, готовят контент. После этого Readium запускает локальный веб-сервер непосредственно в приложении. Этот сервер раздает файлы книги (HTML, CSS, картинки) для воспроизведения в WebView.
Что в итоге? Парсинг EPUB на Flutter на самом деле осуществляется через нативный код Kotlin/Swift. А JS-код в WebView отвечает только за отображение (рендеринг) уже проработанного контента.
Как устроено гибридное Flutter-приложение EPUB
Чтобы создать EPUB-ридер/мобильное приложение, которое сочетает удобство Flutter, мощность нативного кода и гибкость веб-решений, мы реализовали трехуровневую гибридную архитектуру. Это позволило нам добиться высокой производительности, стабильности и гибкого взаимодействия между всеми компонентами продукта.
В общих чертах наша архитектура выглядит так:
1. Flutter (уровень UI)
Все, что видит пользователь, с чем он взаимодействует – это виджеты Flutter: кнопки, панели, настройки, интерактивные элементы и т.д. Сам ридер (модуль для чтения) встраивается в приложение как отдельный компонент в Flutter UI.
2. Native (логика+парсинг EPUB)
На уровне Android (Kotlin) и iOS (Swift) в приложении используется Readium SDK, отвечающий за парсинг. Это “черный ящик”, где происходит вся “магия” вокруг EPUB:
-
Распаковка файла;
-
Парсинг его структуры;
-
Запуск локального веб-сервера, который раздает контент в WebView
3. JavaScript в WebView (рендеринг)
WebView подключается к локальному серверу и загружает EPUB-ридер на базе Readium Web. Именно здесь происходит:
-
Отображение текста;
-
Пагинация;
-
Функционал UI: поиск, выделение, аннотации, изменение шрифтов, тем;
-
Обработка жестов (перелистывание) и т.д.
Как Flutter взаимодействует с JavaScript
Прямого взаимодействия здесь нет. Для настройки связи между Flutter и WebView мы использовали Method Channel – официальный механизм Flutter для обмена данными с нативным кодом. Проще всего будет объяснить эту логику с помощью практических примеров работы читателя с приложением:
- Изменение размера шрифта:
-
Пользователь жмет на кнопку + во Flutter-интерфейсе.
-
Flutter отправляет команду на нативную сторону: {'action': 'setFontSize', 'value': '18px'}.
-
Нативный код (Kotlin/Swift) получает команду и выполняет JS-код в WebView: webView.evaluateJavascript("reader.setFontSize('18px')").
-
Код JS изменяет CSS-стили книги.
-
- Пролистывание страниц
-
Код JS на стороне WebView фиксирует пролистывание и определяет новую позицию в книге.
-
JS вызывает специальную функцию, которая передает данную позицию нативному коду.
-
Нативный код получает позицию и отправляет ее во Flutter.
-
Flutter получает данные и обновляет свой виджет, например, Страница 5 из 120.
-
Такое взаимодействие «Flutter-WebView» в EPUB-ридере может казаться не очень элегантным, но на самом деле оно работает в разы быстрее, чем обработка всех интеракций непосредственно на стороне WebView.
Оптимизация EPUB ридера для Android и iOS
Чтобы обеспечить комфортный опыт в приложении на всех устройствах, мы провели ряд технических оптимизаций, ориентированных на производительность, адаптивность и стабильность гибридной разработки.
- Адаптация интерфейса под разные платформы
Логика виджетов Flutter позволила команде легко адаптировать визуал и функциональность продукта под Android и iOS, соблюдая нативные гайдлайны. Панели управления, кнопки и жесты пролистывания ведут себя одинаково предсказуемо на обеих платформах. Более того, мы учли специфику системных WebView: Safari на iOS и Chrome на Android.
Таблица: Гайдлайны дизайна мобильных приложений для Android и IOS |
Характеристика | Android (Material Design) | iOS (Human Interface Guidelines) |
Дизайн-философия | Яркие цвета, глубина, тени, четкая иерархия элементов | Простота, минимализм, акцент на контент и плавность |
Платформенные отличия UI | Floating Action, насыщенные цвета, акценты | Пространство, чистота, легкость, минимализм |
Компоненты интерфейса | Материальные кнопки, чекбоксы, разделенные поля ввода | Тумблеры, списки настроек, интегрированные текстовые поля |
- Ускорение загрузки страниц EPUB
Высокая производительность EPUB Reader на Flutter была одним из главных требований проекта. Ключом к быстродействию стало разделение парсинга и рендеринга, который мы уже описывали выше. Readium SDK позволил использовать для парсинга нативный код и мгновенно выводить его в WebView. Книги любого объема и сложности открываются мгновенно, словно приложение имеет собственный нативный движок для рендеринга EPUB.
- Безопасность и стабильность
Мы обеспечили:
-
Контролируемый и стабильный обмен данными: Flutter - нативный EPUB reader - WebView. Через Method Channels передаются только минимально необходимые данные (команды, номера страниц).
-
Ограничение доступа к файловой системе: книги не хранятся в открытом виде;
-
Стабильный рендеринг в случае нестандартных EPUB-файлов, ведь Readium SDK "переваривает" даже поврежденные или устаревшие книги.
Дополнительный функционал для читателя электронных книг
Помимо базового отображения контента современный EPUB-ридер должен обеспечивать комфортное, гибкое и персонализированное чтение. В нашем проекте мы реализовали ряд функций, которые делают этот опыт приятным и удобным вне зависимости от предпочтений пользователя или типа устройства.
- Сохранение прогресса и закладок
Читатель может продолжить чтение с того места, где остановился – позиция автоматически сохраняется в памяти приложения. У него есть возможность создавать закладки и свободно переходить между ними через интерфейс ридера. Все данные хранятся локально.
- Поиск по тексту и масштабирование
В кастомном ридере предусмотрена возможность поиска по тексту через ключевые слова и фразы. Пользователь также имеет полную свободу настройки отображения страниц EPUB WebView: выбор и масштабирование шрифтов, изменение размеров, интервалов, полей и т.д. Эти функции работают через Method Channels.
- Темная тема и кастомизация интерфейса
Современное чтение – это чтение в любых условиях: днем, ночью, в транспорте или на улице. Мы реализовали темную тему, выбор цвета фона и текста, а также широкие возможности кастомизации интерфейса. Все стили применяются динамически через CSS и JavaScript, сохраняя при этом скорость рендеринга страниц.
Тестирование и отладка приложения
Продукт оказался для нашей команды несколько нетипичным, поэтому мы уделили особое внимание тестированию, оптимизации и предупреждению возможных проблем совместимости.
- Как протестировать EPUB-ридер?
Прежде всего в арсенале Readium есть набор Test Books. Он содержит набор тестовых EPUB-файлов, охватывающих специфические случаи: устаревшие форматы, поврежденные файлы и т.д. Более того, команда собрала собственную коллекцию EPUB-файлов с разными уровнями сложности: простые книги, экзотические форматы, файлы с нестандартной версткой, книги с множеством изображений, книги со сложными и перегруженными стилями и пр. Мы несколько раз прогнали все наборы через приложение, чтобы убедиться, что оно воспроизводит любые файлы корректно.
- Обеспечение совместимости
В этом направлении команда не столкнулась с существенными проблемами. Прежде всего благодаря тому, что Readium – это стандарт индустрии. Его библиотеки способны "переваривать" практически любые EPUB-файлы и исправлять незначительные проблемы "на лету". Использование системного WebView (Chrome – Android, Safari – iOS) гарантирует, что книга будет корректно открываться и выглядеть одинаково на 99% устройств. С учетом различий между движками Chrome/Safari стили и скрипты дополнительно тестировались отдельно на обеих платформах.
Выводы
Как оказалось, кроссплатформенная разработка мобильного EPUB-ридера – это не просто техническая задача, а настоящий вызов, требующий баланса между производительностью, гибкостью и удобством для пользователя. В кейсе КСД мы реализовали гибридную архитектуру, объединяющую Flutter, нативный код и WebView, чтобы обеспечить стабильную работу с EPUB-контентом без компромиссов в быстродействии или качестве.
Благодаря использованию Readium SDK нам удалось создать быструю и стабильную кроссплатформенную EPUB-читалку, которая работает одинаково хорошо на Android и iOS. Так мы предоставили нашему клиенту продукт достойного качества, а конечным пользователям качественный опыт потребления контента. Издательство КСД обеспечило своим пользователям уникальный сервис и защитило интеллектуальную собственность своих партнеров. Это решение имеет мало аналогов не только на украинском, но и на глобальном рынке.
Команда WEZOM получила в данном проекте уникальный опыт и готова масштабировать и развивать продукт, ведь жизненный путь нового приложения только начался. А если у вас есть дополнительные вопросы по такой разработке или ищете кастомные решения для собственного бизнеса, обращайтесь прямо сейчас! Мы готовы помочь.
FAQ
Как настроить epub.js для работы с Flutter WebView?
Чтобы настроить epub.js во Flutter, нужно загрузить его в WebView через локальный HTML-файл, подключить библиотеку и инициализировать ридер в JavaScript. Но по нашему опыту такая интеграция будет не лучшим решением. Лучше обратить внимание на библиотеки Readium.
Можно ли добавить офлайн-режим в гибридный EPUB ридер?
Да, можно. В нашем кейсе EPUB файл сохраняется локально, а WebView работает через локальный веб-сервер, поэтому весь контент доступен без Интернета. Офлайн-режим реализуется полностью на пользовательском устройстве.
Как реализовать поддержку больших EPUB файлов в Flutter?
В нашем кейсе эту задачу удалось решить благодаря нативному парсингу через Readium SDK. Впрочем, в других проектах эта задача может решаться другими путями – через подбор соответствующей JS-библиотеки или через написание собственного браузерного движка. Всё зависит от потребностей бизнеса.
Какие преимущества гибридного подхода перед нативными EPUB ридерами?
Разница между нативным и гибридным ридером кроется в сложности разработки. Гибридный подход позволяет быстро разработать EPUB-читалку с использованием WebView и готовых библиотек, обеспечивая полную поддержку HTML/CSS. Это экономит ресурсы, упрощает обновление и дает гибкость в кастомизации интерфейса без разработки собственного движка с нуля.
Возможна ли интеграция посторонних плагинов для EPUB во Flutter приложение?
Да, возможна. Посторонние JavaScript-плагины для EPUB можно интегрировать в WebView, а взаимодействие с Flutter реализовать через метод-каналы. Это позволяет расширять функционал ридера без изменений в самом Flutter-приложении.
Как настроить защиту от копирования текста в EPUB ридере?
Чтобы защитить текст от копирования, можно отключить выделение текста и контекстное меню через CSS (user-select: none) и JavaScript. Также WebView позволяет блокировать длинные нажатия. Следует также ограничить доступ к файловой системе и не сохранять EPUB в открытом виде. Защиту могут предоставить специализированные DRM-решения, такие как Readium LCP.
