Большая часть расширенного текстового содержимого веб-приложений хранится в виде строк HTML, и, естественно, не возникает проблем с отображением содержимого HTML в документах HTML. Однако в апплете WeChat (далее «мини-программа») как должна отображаться эта часть контента?
решение
wxParse
Когда апплет был впервые запущен, было невозможно напрямую отображать HTML-контент, поэтому родилась библиотека под названием «wxParse». Его принцип заключается в том, чтобы разобрать код HTML на древовидные данные, а затем отобразить данные с помощью шаблона апплета.
rich-text
Позже апплет добавил компонент «форматированный текст» для отображения содержимого форматированного текста. Однако этот компонент имеет огромное ограничение:События всех узлов блокируются в компоненте. Другими словами, в этом компоненте не может быть реализована даже такая простая функция, как «предпросмотр изображения».
web-view
Позже апплет позволяет вкладывать веб-страницы через компонент «веб-просмотр», а отображение содержимого HTML через веб-страницы является наиболее совместимым решением. Однако производительность низкая, потому что загружается еще одна страница.
Когда «WePY» встречается с «wxParse»
Основываясь на рассмотрении пользовательского опыта и функционального взаимодействия, мы отказались от двух нативных компонентов «форматированный текст» и «веб-представление» и выбрали «wxParse». Однако после его использования я обнаружил, что «wxParse» не очень хорошо отвечает потребностям:
- Наш апплет разработан на основе фреймворка "WePY", а "wxParse" написан на основе нативного апплета. Чтобы сделать их совместимыми, исходный код "wxParse" должен быть изменен.
- «wxParse» просто отображает и предварительно просматривает изображение исходного элемента img через компонент изображения. При фактическом использовании интерфейс облачного хранилища может использоваться для уменьшения изображения для достижения «Отображение с уменьшенным изображением, предварительный просмотр с исходным изображением"цель.
- "wxParse" напрямую использует видео компонент апплета для отображения видео, но видео компонентПроблема иерархииЧасто вызывает исключения пользовательского интерфейса (например, блокирует элемент с фиксированным положением).
Кроме того, просмотр репозитория кода «wxParse» показывает, что он не обновлялся два года. Поэтому родилась идея переписать компонент расширенного текста на основе шаблона компонента «WePY», и результатом стал проект «WePY HTML».
Процесс реализации
Разобрать HTML
Во-первых, все еще нужно разобрать строку HTML на древовидные данные, я использую «специальный метод разделения символов». Специальные символы в HTML — это «», первый из которых является начальным, а второй — конечным.
- Если анализируемый контент начинается с начального символа, перехватить егоОт начала до концаСодержимое анализируется как узел.
- Если анализируемый контент не начинается с начального символа, перехватить егоначать, чтобы начать персонаж(или конец, если начальный символ отсутствует) анализируется как обычный текст.
- Оставшийся контент переходит к следующему раунду синтаксического анализа до тех пор, пока не останется остаточного контента.
Как показано на изображении ниже:
Для формирования древовидной структуры узел контекста (по умолчанию корневой узел) поддерживается в процессе синтаксического анализа:
- Если перехваченное содержимое является начальным тегом, создайте дочерний узел под текущим узлом контекста в соответствии с совпавшим именем и атрибутом тега. Если тег не является самозакрывающимся тегом (br, img и т. д.), сделайте узел контекста новым узлом.
- Если перехваченное содержимое является конечным тегом, закройте текущий узел контекста в соответствии с именем тега (установите узел контекста в качестве его родительского узла).
- Если это обычный текст, текстовый узел создается под текущим узлом контекста, а узел контекста остается неизменным.
Процесс показан в таблице ниже:
контекст (перед синтаксическим анализом) | Разобрать содержимое | контекст (после разбора) |
---|---|---|
корневой узел | <div class="content"> | div |
div | <p style="text-indent: 2em;"> | p |
p | Hello world | p |
p | </p> | div |
div | </div> | корневой узел |
После вышеуказанного процесса строка HTML проанализируется в дереве узла.
В сравнении
Сравните приведенный выше алгоритм с другими подобными алгоритмами синтаксического анализа (производительность измеряется путем «анализа HTML-кода длиной 10 000»):
Контраст | Алгоритм этого компонента | wxParse | parse5 |
---|---|---|---|
представление | 3~6ms | около 20 мс | около 20 мс |
Отказоустойчивость | Разница | в общем | мощный |
Размер файла (без сжатия) | 6kb | 22kb | около 400кб |
Видно, что алгоритм этого компонента имеет подавляющее преимущество по сравнению с двумя другими без учета отказоустойчивости (выдача неправильных результатов вместо выдачи исключений), что соответствует небольшой программе "маленький и быстрый"потребности. В нормальных условиях код, сгенерированный редактором форматированного текста, не будет содержать синтаксических ошибок. Так что даже с меньшей отказоустойчивостью это не большая проблема (но это требует улучшения).
Рендеринг шаблона
Рендеринг древовидной структуры неизбежно будет включать подузлырекурсияиметь дело с. Однако шаблон апплета не поддерживает рекурсию, что, похоже, ведет к большой яме.
Глядя на реализацию шаблона "wxParse" решает эту проблему просто и грубо: вложенные вызовы через 13 почти одинаковых шаблонов (1 вызов 2, 2 вызов 3, ..., 12 вызов 13), т.е. скажем, он может поддерживать до 12 вложений. В общем, этой глубины тоже достаточно.
Так как сам фреймворк "WEPY" имеет механизм сборки, вам не нужно писать один и тот же шаблон практически с одной моделью, вы можете сгенерировать его через встроенный плагин.
Ниже приведен шаблон, который необходимо многократно вкладывать (упрощенно), вставлять специальные комментарии до и после начала его кода, чтобы идентифицировать его, и использовать другой логотип специального комментария («»):
<!-- wepyhtml-repeat start -->
<template name="wepyhtml-0">
<block wx:if="{{ content }}" wx:for="{{ content }}">
<block wx:if="{{ item.type === 'node' }}">
<view class="wepyhtml-tag-{{ item.name }}">
<!-- next template -->
</view>
</block>
<block wx:else>{{ item.text }}</block>
</block>
</template>
<!-- wepyhtml-repeat end -->
Ниже приведен соответствующий код сборки (необходимо установить "wepy-plugin-replace»):
// wepy.config.js
{
plugins: {
replace: {
filter: /\.wxml$/,
config: {
find: /<\!-- wepyhtml-repeat start -->([\W\w]+?)<\!-- wepyhtml-repeat end -->/,
replace(match, tpl) {
let result = '';
// 反正不要钱,直接写个20层嵌套
for (let i = 0; i <= 20; i++) {
result += '\n' + tpl
.replace('wepyhtml-0', 'wepyhtml-' + i)
.replace(/<\!-- next template -->/g, () => {
return i === 20 ?
'' :
`<template is="wepyhtml-${ i + 1 }" wx:if="{{ item.children }}" data="{{ content: item.children"></template>`;
});
}
return result;
}
}
}
}
}
Однако после его запуска было обнаружено, что узлы второго и более глубоких уровней не отрисовывались, что свидетельствует о сбое вложенности. Глядя на файл wxml, созданный в каталоге dist, вы можете увидеть, что имя переменной не совпадает с исходным кодом компонента:
<block wx:if="{{ $htmlContent$wepyHtml$content }}" wx:for="{{ $htmlContent$wepyHtml$content }}">
Когда "WePY" генерирует код компонента, во избежание конфликта имени переменной между данными компонента и данными страницы, он будетДобавлять префикс к имени переменной компонента по определенным правилам(как в приведенном выше коде "wepyHtml$"). Поэтому при создании вложенных шаблонов вы также должны использовать имена переменных с префиксом.
Сначала добавьте переменную thisIsMe в код компонента, чтобы определить префикс:
<!-- wepyhtml-repeat start -->
<template name="wepyhtml-0">
{{ thisIsMe }}
<block wx:if="{{ content }}" wx:for="{{ content }}">
<block wx:if="{{ item.type === 'node' }}">
<view class="wepyhtml-tag-{{ item.name }}">
<!-- next template -->
</view>
</block>
<block wx:else>{{ item.text }}</block>
</block>
</template>
<!-- wepyhtml-repeat end -->
Затем измените код сборки:
replace(match, tpl) {
let result = '';
let prefix = '';
// 匹配 thisIsMe 的前缀
tpl = tpl.replace(/\{\{\s*(\$.*?\$)thisIsMe\s*\}\}/, (match, p) => {
prefix = p;
return '';
});
for (let i = 0; i <= 20; i++) {
result += '\n' + tpl
.replace('wepyhtml-0', 'wepyhtml-' + i)
.replace(/<\!-- next template -->/g, () => {
return i === 20 ?
'' :
`<template is="wepyhtml-${ i + 1 }" wx:if="{{ item.children }}" data="{{ ${ prefix }content: item.children }}"></template>`;
});
}
return result;
}
На данный момент проблема рендеринга решена.
картина
Чтобы сэкономить трафик и повысить скорость загрузки, при отображении форматированного текстового содержимого внутреннее изображение обычно уменьшается в соответствии с требуемым размером, а исходное изображение отображается только при нажатии на маленькое изображение для предварительного просмотра. В основном это включает изменение свойств узла:
- Сохраните исходный путь к изображению (значение атрибута src) в пользовательском атрибуте (например, «data-src») и добавьте его в массив изображений предварительного просмотра.
- Измените значение атрибута src изображения на сокращенный URL-адрес изображения (обычно поставщики облачных услуг предоставляют такие правила для URL-адресов).
- При нажатии на изображение предварительно отображается значение пользовательского свойства.
Чтобы выполнить это требование, этот компонент предоставляет ловушку при разборе узлов (onNodeCreate):
onNodeCreate(name, attrs) {
if (name === 'img') {
attrs['data-src'] = attrs.src;
// 预览图数组
this.previewImgs.push(attrs.src);
// 缩图
attrs.src = resizeImg(attrs.src, 640);
}
}
Соответствующий шаблон и логика обработки событий выглядят следующим образом:
<template name="wepyhtml-img">
<image class="wepyhtml-tag-img" mode="widthFix" src="{{ elem.attrs.src }}" data-src="{{ elem.attrs['data-src'] || elem.attrs.src }}" @tap="imgTap"></image>
</template>
// 点击小图看大图
imgTap(e) {
wepy.previewImage({
current: e.currentTarget.dataset.src,
urls: this.previewImgs
});
}
видео
В апплете уровень видеосоставляющей выше (и понизить нельзя). Если в дизайне страницы есть элементы, которые могут блокировать видео, для борьбы с ними требуются определенные навыки:
- Скрыть видеокомпонент и занять место имиджевого компонента (обложка видео);
- Когда изображение нажато, пусть видео воспроизводится в полноэкранном режиме;
- Приостанавливает воспроизведение при выходе из полноэкранного режима.
Соответствующий код выглядит следующим образом:
<template name="wepyhtml-video">
<view class="wepyhtml-tag-video" @tap="videoTap" data-nodeid="{{ elem.nodeId }}">
<!-- 视频封面 -->
<image class="wepyhtml-tag-img wepyhtml-tag-video__poster" mode="widthFix" src="{{ elem.attrs.poster }}"></image>
<!-- 播放图标 -->
<image class="wepyhtml-tag-img wepyhtml-tag-video__play" src="./imgs/icon-play.png"></image>
<!-- 视频组件 -->
<video style="display: none;" src="{{ elem.attrs.src }}" id="wepyhtml-video-{{ elem.nodeId }}" @fullscreenchange="videoFullscreenChange" @play="videoPlay"></video>
</view>
</template>
{
// 点击封面图,播放视频
videoTap(e) {
const nodeId = e.currentTarget.dataset.nodeid;
const context = wepy.createVideoContext('wepyhtml-video-' + nodeId);
context.play();
// 在安卓微信下,如果视频不可见,则调用play()也无法播放
// 需要再调用全屏方法
if (wepy.getSystemInfoSync().platform === 'android') {
context.requestFullScreen();
}
},
// 视频层级较高,为防止遮挡其他特殊定位元素,造成界面异常,
// 强制全屏播放
videoPlay(e) {
wepy.createVideoContext(e.currentTarget.id).requestFullScreen();
},
// 退出全屏则暂停
videoFullscreenChange(e) {
if (!e.detail.fullScreen) {
wepy.createVideoContext(e.currentTarget.id).pause();
}
}
}
Открытый исходный код Наконец, вставьте репозиторий проекта «WePY HTML»:GitHub.com/Подготовка-Веб…, см. README в проекте для конкретного использования. Если у вас возникнут проблемы во время использования или у вас есть хорошие предложения и комментарии, вы можете отправить их в разделе «Вопросы».
(Эта статья также была опубликована в личном блоге автораМуронг Луо.life/статья/Порядочность…)