Как фронтенд-инженер, разработавший несколько проектов H5, неизбежно столкнется с некоторыми проблемами совместимости, такими как перелезание через яму в процессе разработки . Сейчас я последовательно обобщу эти проблемы, и приведу принцип генерации питов и условную схему заполнения питов на данном этапе . Вот пошаговое резюме.
Регулярная работа, ставьте лайк и смотрите еще раз! Ваши лайки - одна из мотиваций для моего творчества!
вопрос
Ниже приведены некоторые распространенные проблемы, с которыми я столкнулся. Если вы столкнетесь с другими проблемами, пожалуйста, добавьте их в область комментариев, и я добавлю их после практики. Спасибо! (почаще обновляйте эту статью)
Сводка проблем, связанных с мобильным H5:
-
1px проблема -
Адаптивный макет -
Свайп iOS не плавный -
Раскрывающийся список границ подтягивания iOS отображается белым пустым -
Виджет страницы увеличивает или уменьшает масштаб неопределенного поведения -
щелчок Щелчок проникновения и задержки -
Мягкая клавиатура всплывает и толкает страницу вверх, но не вниз -
Проблема адаптации нижней панели iPhone X -
Сохранение страниц в виде картинок и QR-кодов, проблемы и решения -
Проблема с общим аккаунтом WeChat H5 -
H5 называет проблемы и решения, связанные с SDK -
Схемы и стратегии, связанные с отладкой H5
Обзор основных технологий, связанных с мобильным H5
Принципы и практика
В предыдущих двух статьях подробно обсуждалась проблема 1px и проблема адаптивного макета , а также давались принципы и решения.
Чтобы предотвратить потерю, перейдите на канал быстрого доступа после лайка и избранного : **1px** [1] канал и адаптивный макет [2] канал .
Далее давайте рассмотрим принципы и решения других задач.
Следующие растворы были успешно протестированы мной и безопасны для здоровья. Пожалуйста, отложите их и съешьте. Из-за недостатка места детали реализации некоторых непрофильных решений пока не обсуждались и должны быть изучены самостоятельно.
Свайп iOS не плавный
Производительность
Скольжение страницы вверх и вниз вызовет зависание, палец оторвется от страницы, и страница немедленно перестанет двигаться. Общая характеристика заключается в том, что скольжение не является плавным и отсутствует инерция скольжения.
причина
Почему свайп не плавный в веб-просмотре iOS и как он определяется?
В конце концов я нашел ответ в safari
документации (ссылка на документацию находится в разделе "Ресурсы").
Оказывается, в iOS 5.0 и более поздних версиях для скольжения определены два значения, auto
а touch
значение по умолчанию — auto
.
-webkit-overflow-scrolling: touch; /* 当手指从触摸屏上移开,会保持一段时间的滚动 */
-webkit-overflow-scrolling: auto; /* 当手指从触摸屏上移开,滚动会立即停止 */
复制代码
решение
1. Добавьте сенсорный метод прокрутки в контейнер прокрутки.
Установите -webkit-overflow-scrolling
значениеtouch
.wrapper {
-webkit-overflow-scrolling: touch;
}
复制代码
Установите полосу прокрутки, чтобы скрыть:
.container ::-webkit-scrollbar {display: none;}
Может привести к тому , что position:fixed;
элементы с фиксированным позиционированием будут прокручиваться вместе со страницей.
2. Установить переполнение
Установите external overflow
на hidden
, установите элемент содержимого overflow
на auto
. Если внутренний элемент выходит за пределы тела, происходит прокрутка, а часть за пределами тела скрывается.
body {
overflow-y: hidden;
}
.wrapper {
overflow-y: auto;
}
复制代码
Сочетание того и другого еще лучше!
Раскрывающийся список границ подтягивания iOS отображается белым пустым
Производительность
Нажмите и удерживайте палец на экране, чтобы потянуть вниз, и в верхней части экрана появится белая область. Нажмите и удерживайте палец на экране и потяните вверх, внизу появится лишняя белая область.
причина
В iOS проведите пальцем вверх и вниз по экрану, чтобы запустить touchmove
событие . Объектом, запускаемым этим событием, является весь webview
контейнер , естественно, контейнер будет перетаскиваться, а остальное будет пустым.
решение
1. Отслеживайте события, чтобы запретить скольжение
Существует три события мобильного касания, которые определяются как
1. touchstart :手指放在一个DOM元素上。
2. touchmove :手指拖曳一个DOM元素。
3. touchend :手指从一个DOM元素上移开。
复制代码
Очевидно, что нам нужно контролировать touchmove
событие , поэтому я нашел этот отрывок в документации W3C.
Обратите внимание, что скорость, с которой пользовательский агент отправляет события touchmove, определяется реализацией и может зависеть от возможностей оборудования и других деталей реализации.
Если метод preventDefault вызывается при первом событии touchmove активной точки касания, он должен предотвращать любое действие по умолчанию, вызванное любым событием touchmove, связанным с той же активной точкой касания, например прокрутку.
touchmove
Скорость событий определяется реализацией и зависит от возможностей оборудования и других деталей реализации.
preventDefault
метод, который предотвращает все действия по умолчанию, такие как прокрутка, в одной и той же точке взаимодействия.
Исходя из этого, мы нашли решение, через мониторинг touchmove
, позволять местам, которые нужно смахивать, скользить, а местам, которые не нужно скользить, запрещать скольжение.
Стоит отметить, что мы хотим отфильтровать элементы с контейнерами прокрутки.
Реализация выглядит следующим образом:
document.body.addEventListener('touchmove', function(e) {
if(e._isScroller) return;
// 阻止默认事件
e.preventDefault();
}, {
passive: false
});
复制代码
2. Компромиссы прокрутки заполняют пробелы и украшают другими функциями
Во многих случаях мы не можем решить эту проблему и изменить свой образ мышления. В зависимости от сценария мы можем использовать раскрывающийся список как функциональную операцию .
Например: обновить страницу после потянув вниз
Увеличение или уменьшение масштаба страницы неопределенное поведение
Производительность
Дважды коснитесь или сведите пальцы на элементе страницы, и страница увеличится или уменьшится.
причина
Сам HTML будет генерировать поведение увеличения или уменьшения масштаба Например, в браузерах ПК вы можете свободно управлять увеличением или уменьшением масштаба страницы. Но на мобильных устройствах нам такое поведение не нужно. Поэтому нам нужно запретить это неопределенное поведение, чтобы улучшить взаимодействие с пользователем.
Принципы и решения
В стандарте метатега HTML meta
есть средний viewport
атрибут , который используется для управления масштабом страницы, который обычно используется на мобильных терминалах. Как показано на следующем рисунке, MDN
Мобильный терминал обычным письмом
<meta name="viewport" content="width=device-width, initial-scale=1.0">
复制代码
Таким образом, мы можем установить maximum-scale
и использовать, чтобыminimum-scale
избежать этой проблемыuser-scalable=no
<meta name=viewport
content="width=device-width, initial-scale=1.0, minimum-scale=1.0 maximum-scale=1.0, user-scalable=no">
复制代码
задержка события клика и проникновение
Производительность
Слушайте click
события и щелкните элемент, чтобы активировать временную задержку прибл 300ms
.
Щелкните слой маски, после того, как слой маски исчезнет, нижележащий элемент щелкнет для срабатывания.
причина
Почему есть задержка клика?
В сафари в iOS, чтобы реализовать операцию масштабирования двойным щелчком, после 300 мс щелчка, если второй щелчок не выполняется, выполняется операция click
щелчка . То есть определить, вызвано ли поведение пользователя двойным щелчком мыши. click
Однако в приложении существует задержка в 300 мс при одиночном нажатии независимо от того, требуется ли двойное касание для увеличения .
Почему клик кликает?
При наложении двухслойных элементов события привязываются к верхнему элементу, а touch
события к нижнему элементу click
. Так как click
происходит touch
после нажатия на верхний элемент, элемент исчезнет, а нижний элемент вызовет click
событие , которое производит эффект проникновения клика.
Принципы и решения
Решение 1. Замените щелчок на сенсорный запуск
Как упоминалось ранее, мобильные устройства поддерживают не только клики, но и несколько сенсорных событий. Итак, наша основная идея сейчас состоит в том, чтобы touch
заменить click
событиями.
click
Замена на touchstart
не только решает проблему задержки click
событий , но и решает проблему проникновения. Потому что проблема проникновения возникает, когда touch
и click
смешиваются.
использовать в родном
el.addEventListener("touchstart", () => { console.log("ok"); }, false);
复制代码
использовать в vue
<button @touchstart="handleTouchstart()">点击</button>
复制代码
В решениях с открытым исходным кодом предусмотрены как click
события и события touchstart
. как button
компоненты
Итак, можно ли заменить все click
события на touchstart
? Почему фреймворки с открытым исходным кодом все еще дают click
события ?
Давайте представим ситуацию, которая требует одновременного нажатия и смахивания. А если click
заменить на touchstart
?
Последовательность срабатывания события:
touchstart
,touchmove
,touchend
,click
.
Легко представить, что когда мне нужно touchmove
скользить, событие клика, которое запускается первым touchstart
, конфликтует?
Поэтому в случае прокрутки рекомендуется использовать click
обработку .
fastclick
Следующая обработка также выполняется в следующей библиотеке с открытым исходным кодом. Часть исходного кода была перехвачена для touchstart
и touchend
.
// If the target element is a child of a scrollable layer (using -webkit-overflow-scrolling: touch) and:
// 1) the user does a fling scroll on the scrollable layer
// 2) the user stops the fling scroll with another tap
// then the event.target of the last 'touchend' event will be the element that was under the user's finger
// when the fling scroll was started, causing FastClick to send a click event to that layer - unless a check
// is made to ensure that a parent layer was not scrolled before sending a synthetic click (issue #42).
this.updateScrollParent(targetElement);
复制代码
// Don't send a synthetic click event if the target element is contained within a parent layer that was scrolled
// and this tap is being used to stop the scrolling (usually initiated by a fling - issue #42).
scrollParent = targetElement.fastClickScrollParent;
if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) {
return true;
}
复制代码
Основная цель — убедиться, что он не находится под прокручиваемым родительским элементом при использовании touchstart
синтетического события.click
Решение 2. Используйте библиотеку fastclick
использовать после npm/yarn
установки
import FastClick from 'fastclick';
FastClick.attach(document.body, options);
复制代码
Точно так же fastclick
после использования библиотеки click
исчезли проблемы с задержкой и проникновением
По моей практике, раз речь идет о открытой библиотеке, то надо понимать принцип ее реализации. В основном он инкапсулирует существующую собственную коллекцию событий в более совместимую коллекцию событий.
Исходный код fastclick [3] Основной код не длинный, менее 1000 строк. Интересно узнать!
Мягкая клавиатура толкает страницу вверх, но не вниз
Производительность
В телефоне Android при нажатии input
на поле клавиатура всплывает, и страница выталкивается вверх, что приводит к беспорядочному стилю страницы.
При удалении фокуса клавиатура убирается, область клавиатуры остается пустой и не откидывается назад.
причина
У нас будет фиксированное дно в макете приложения. В некоторых версиях Android всплывающее окно ввода распаковывает absolute
и fixed
находит элементы. Видимая область становится меньше, а макет загромождается.
Принципы и решения
Решение для программной клавиатуры, толкающей страницу вверх, в основном заключается в отслеживании изменения высоты страницы и принудительном восстановлении ее высоты до всплывающего окна.
// 记录原有的视口高度
const originalHeight = document.body.clientHeight || document.documentElement.clientHeight;
window.onresize = function(){
var resizeHeight = document.documentElement.clientHeight || document.body.clientHeight;
if(resizeHeight < originalHeight ){
// 恢复内容区域高度
// const container = document.getElementById("container")
// 例如 container.style.height = originalHeight;
}
}
复制代码
Проблема, связанная с тем, что клавиатура не может вернуться назад, возникает в iOS 12+ и wechat 6.7.4+ и является относительно распространенной ошибкой в разработке WeChat H5.
Принцип совместимости, 1. Определить тип версии 2. Изменить видимую область прокрутки
const isWechat = window.navigator.userAgent.match(/MicroMessenger\/([\d\.]+)/i);
if (!isWechat) return;
const wechatVersion = wechatInfo[1];
const version = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);
// 如果设备类型为iOS 12+ 和wechat 6.7.4+,恢复成原来的视口
if (+wechatVersion.replace(/\./g, '') >= 674 && +version[1] >= 12) {
window.scrollTo(0, Math.max(document.body.clientHeight, document.documentElement.clientHeight));
}
复制代码
window.scrollTo(x-coord, y-coord)
, которыйwindow.scrollTo(0, clientHeight)
возвращается к исходному видовому экрану
Проблема с адаптацией безопасной зоны iPhone серии X
Производительность
По бокам или внизу челки на голове есть челка, закрывающая текст, или пустое место на черном или белом фоне.
причина
В iPhone X и вышестоящей серии используется дизайн Liu Haiping и полноэкранные жесты . Голова, низ и бока нуждаются в специальной обработке. Чтобы адаптироваться к особой ситуации iPhone X.
решение
Настройте безопасную область, заполните опасную область и не выполняйте операции и не отображайте содержимое в опасной области.
Опасная область относится к неправильной области головы, области горизонтальной полосы внизу, а также левой и правой триггерным областям.
Конкретная операция: viewport-fit
meta
установить метку на cover
, получить заливку всех областей. Определите, принадлежит ли устройство iPhone X, и добавьте адаптационный слой в нижнюю часть головы.
viewport-fit
Есть 3 значения:
auto
: это значение не влияет на исходный порт просмотра макета, и вся веб-страница доступна для просмотра.contain
: область просмотра масштабируется, чтобы соответствовать самому большому прямоугольнику, отображаемому в строке.cover
: область просмотра масштабируется для заполнения экрана устройства. Настоятельно рекомендуется использоватьsafe area inset
переменные чтобы важный контент не отображался за пределами экрана.
Установите для окна просмотра значениеcover
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes, viewport-fit=cover">
复制代码
Добавьте адаптационный слой
使用 safe area inset
变量
/* 适配 iPhone X 顶部填充*/
@supports (top: env(safe-area-inset-top)){
body,
.header{
padding-top: constant(safe-area-inset-top, 40px);
padding-top: env(safe-area-inset-top, 40px);
padding-top: var(safe-area-inset-top, 40px);
}
}
/* 判断iPhoneX 将 footer 的 padding-bottom 填充到最底部 */
@supports (bottom: env(safe-area-inset-bottom)){
body,
.footer{
padding-bottom: constant(safe-area-inset-bottom, 20px);
padding-bottom: env(safe-area-inset-bottom, 20px);
padding-top: var(safe-area-inset-bottom, 20px);
}
}
复制代码
safe-area-inset-top
,safe-area-inset-right
,safe-area-inset-bottom
,safe-area-inset-left
safe-area-inset-*
由四个定义了视口边缘内矩形的top
,right
,bottom
和left
的环境变量组成,这样可以安全地放入内容,而不会有被非矩形的显示切断的风险。对于矩形视口,例如普通的笔记本电脑显示器,其值等于零。 对于非矩形显示器(如圆形表盘,iPhoneX
屏幕),在用户代理设置的四个值形成的矩形内,所有内容均可见。
其中 env()
用法为 env( <custom-ident> , <declaration-value>? )
,第一个参数为自定义的区域,第二个为备用值。
其中 var()
用法为 var( <custom-property-name> , <declaration-value>? )
,作用是在 env()
不生效的情况下,给出一个备用值。
constant()
被 css
2017-2018 年为草稿阶段,是否已被标准化未知。而其他iOS 浏览器版本中是否有此函数未知,作为兼容处理而添加进去。
详情请查看文章末尾的参考资料。
兼容性
页面生成为图片和二维码问题
表现
在工作中有需要将页面生成图片或者二维码的需求。可能我们第一想到的,交给后端来生成更简单。但是这样我们需要把页面代码全部传给后端,网络性能消耗太大。
解决方案
生成二维码
使用 QRCode 生成二维码
import QRCode from 'qrcode';
// 使用 async 生成图片
const options = {};
const url = window.location.href;
async url => {
try {
console.log(await QRCode.toDataURL(url, options))
} catch (err) {
console.error(err);
}
}
复制代码
将 await QRCode.toDataURL(url, options)
赋值给 图片 url
即可
生成图片
主要是使用 htmlToCanvas
生成 canvas
画布
import html2canvas from 'html2canvas';
html2canvas(document.body).then(function(canvas) {
document.body.appendChild(canvas);
});
复制代码
但是不单单在此处就完了,由于是 canvas
的原因。移动端生成出来的图片比较模糊。
我们使用一个新的 canvas
方法多倍生成,放入一倍容器里面,达到更加清晰的效果,通过超链接下载图片 下载文件简单实现,更完整的实现方式之后更新
const scaleSize = 2;
const newCanvas = document.createElement("canvas");
const target = document.querySelector('div');
const width = parseInt(window.getComputedStyle(target).width);
const height = parseInt(window.getComputedStyle(target).height);
newCanvas.width = width * scaleSize;
newCanvas.height = widthh * scaleSize;
newCanvas.style.width = width + "px";
newCanvas.style.height =width + "px";
const context = newCanvas.getContext("2d");
context.scale(scaleSize, scaleSize);
html2canvas(document.querySelector('.demo'), { canvas: newCanvas }).then(function(canvas) {
// 简单的通过超链接设置下载功能
document.querySelector(".btn").setAttribute('href', canvas.toDataURL());
}
复制代码
根据需要设置
scaleSize
大小
微信公众号分享问题
表现
在微信公众号 H5 开发中,页面内部点击分享按钮调用 SDK,方法不生效。
解决方案
解决方法:添加一层蒙层,做分享引导。
因为页面内部点击分享按钮无法直接调用,而分享功能需要点击右上角更多来操作。
然后用户可能不知道通过右上角小标里面的功能分享。又想引导用户分享,这时应该怎么做呢?
技术无法实现的,从产品出发。
如果技术上实现复杂,或者直接不能实现。不要强行钻牛角尖哦,学会怼产品,也是程序员必备的能力之一。
H5 调用 SDK 相关解决方案
产生原因
在 Hybrid App 中使用 H5 是最常见的不过了,刚接触的,肯定会很生疏模糊。不知道 H5 和 Hybrid 是怎么交互的。怎样同时支持 iOS 和 Android 呢?现在来谈谈 Hybrid 技术要点,原生与 H5 的通信。
解决方案
使用 DSBridge
同时支持 iOS 与 Android
文档见参考资料
SDK小组 提供方法
-
注册方法 `bridge.register`
bridge.register('enterApp', function() {
broadcast.emit('ENTER_APP')
})
复制代码
-
回调方法 `bridge.call`
export const getSDKVersion = () => bridge.call('BLT.getSDKVersion')
复制代码
事件监听与触发法
const broadcast = {
on: function(name, fn, pluralable) {
this._on(name, fn, pluralable, false)
},
once: function(name, fn, pluralable) {
this._on(name, fn, pluralable, true)
},
_on: function(name, fn, pluralable, once) {
let eventData = broadcast.data
let fnObj = { fn: fn, once: once }
if (pluralable && Object.prototype.hasOwnProperty.call(eventData, 'name')) {
eventData[name].push(fnObj)
} else {
eventData[name] = [fnObj]
}
return this
},
emit: function(name, data, thisArg) {
let fn, fnList, i, len
thisArg = thisArg || null
fnList = broadcast.data[name] || []
for (i = 0, len = fnList.length; i < len; i++) {
fn = fnList[i].fn
fn.apply(thisArg, [data, name])
if (fnList[i].once) {
fnList.splice(i, 1)
i--
len--
}
}
return this
},
data: {}
}
export default broadcast
复制代码
踩坑注意
方法调用前,一定要判断 SDK 是否提供该方法 如果 Android 提供该方法,iOS 上调用就会出现一个方法调用失败等弹窗。 怎么解决呢?
提供一个判断是否 Android、iOS。根据设备进行判断
export const hasNativeMethod = (name) =>
return bridge.hasNativeMethod('BYJ.' + name)
}
export const getSDKVersion = function() {
if (hasNativeMethod('getSDKVersion')) {
bridge.call('BYJ.getSDKVersion')
}
}
复制代码
同一功能需要iOS,Android方法名相同,这样更好处理哦
H5 调试相关方案策略
表现
调试代码一般就是为了查看数据和定位 bug。分为两种场景,一种是开发和测试时调试,一种是生产环境上调试。
为什么有生产环境上调试呢?有些时候测试环境上没法复现这个 bug,测试环境和生产环境不一致,此时就需要紧急生产调试。
在 PC 端开发时,我们可以直接掉出控制台,使用浏览器提供的工具操作devtools或者查看日志。但是在 App 内部我们怎么做呢?
原理与解决方案
1. vconsole
控制台插件
使用方法也很简单
import Vconsole from 'vconsole'
new Vconsole()
复制代码
有兴趣看看它实现的基本原理,我们关注的点应该在 vsconsole 如何打印出我们所有 log 的 腾讯开源vconsole[4]
上述方法仅用于开发和测试。生产环境中不允许出现,所以,使用时需要对环境进行判断。
import Vconsole from 'vconsole'
if (process.env.NODE_ENV !== 'production') {
new Vconsole()
}
复制代码
2. 代理 + spy-debugger
操作稍微有点麻烦,不过我会详细写出,大致分为 4 个步骤
-
安装插件\(全局安装\)
sudo npm install spy-debugger -g
复制代码
-
手机与电脑置于同一 wifi 下,手机设置代理
设置手机的 HTTP 代理,代理 IP 地址设置为 PC 的 IP 地址,端口为spy-debugger
的启动端口
spy-debugger 默认端口:9888
Android :设置 - WLAN - 长按选中网络 - 修改网络 - 高级 - 代理设置 - 手动
IOS :设置 - Wi-Fi - 选中网络, 点击感叹号, HTTP 代理手动
-
手机打开浏览器或者 app 中 H5 页面 -
打开桌面日志网站进行调试,点击 npm 控制台监听地址。查看抓包和 H5 页面结构
这种方式可以调试生成环境的页面,不需要修改代码,可以应付大多数调试需求
总结
本篇文章耗费作者一个多星期的业余时间,存手工敲打 4500 +字,同时收集,整理之前很多坑点和边写作边思考和总结。如果能对你有帮助,便是它最大的价值。都看到这里还不点赞,太过不去啦!😄
由于技术水平有限,文章中如有错误地方,请在评论区指出,感谢!
关于移动端 H5 的文章告一段落了,之后实践中遇到的问题都将在此文中更新。另外准备做一个移动端 H5 开源项目。多关注下 我的github[5]动态哦!
之后,应该回去研究下开源和面试题相关内容分享,想持续了解更多,不妨点赞和关注呗。
参考资料
-
Safari CSS Reference[6] -
MDN touch 事件 -
MDN css var\(\)[7] -
MDN css env\(\)[8] -
csswg env\(\) drafts[9] -
fastclick 源码[10] -
DSBridge-Android[11] & DSBridge-iOS[12] -
qrcodejs 源码[13] -
html2canvas 源码[14] -
关于H5页面在iPhoneX适配[15] -
vant 相关文档[16]
参考资料
https://juejin.cn/post/6844904023145857038: https://juejin.cn/post/6844904023145857038
[2]
https://juejin.cn/post/6844904021552005128: https://juejin.cn/post/6844904021552005128
[3]
https://github.com/ftlabs/fastclick/blob/master/lib/fastclick.js: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fftlabs%2Ffastclick%2Fblob%2Fmaster%2Flib%2Ffastclick.js
[4]
https://github.com/Tencent/vConsole/blob/dev/src/core/core.js: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2FTencent%2FvConsole%2Fblob%2Fdev%2Fsrc%2Fcore%2Fcore.js
[5]
https://github.com/suoyuesmile/suo-blog: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fsuoyuesmile%2Fsuo-blog
[6]
https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariCSSRef/Articles/StandardCSSProperties.html#//apple_ref/css/property/-webkit-overflow-scrolling: https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.apple.com%2Flibrary%2Farchive%2Fdocumentation%2FAppleApplications%2FReference%2FSafariCSSRef%2FArticles%2FStandardCSSProperties.html%23%2F%2Fapple_ref%2Fcss%2Fproperty%2F-webkit-overflow-scrolling
[7]
https://developer.mozilla.org/zh-CN/docs/Web/CSS/var: https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FCSS%2Fvar
[8]
https://developer.mozilla.org/zh-CN/docs/Web/CSS/env: https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FCSS%2Fenv
[9]
https://drafts.csswg.org/css-env-1/: https://link.juejin.cn?target=https%3A%2F%2Fdrafts.csswg.org%2Fcss-env-1%2F
[10]
https://github.com/ftlabs/fastclick/blob/master/lib/fastclick.js: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fftlabs%2Ffastclick%2Fblob%2Fmaster%2Flib%2Ffastclick.js
[11]
https://github.com/wendux/DSBridge-Android: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fwendux%2FDSBridge-Android
[12]
https://github.com/wendux/DSBridge-IOS: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fwendux%2FDSBridge-IOS
[13]
https://github.com/davidshimjs/qrcodejs: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fdavidshimjs%2Fqrcodejs
[14]
https://github.com/niklasvh/html2canvas: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fniklasvh%2Fhtml2canvas
[15]
https://www.cnblogs.com/lolDragon/p/7795174.html: https://link.juejin.cn?target=https%3A%2F%2Fwww.cnblogs.com%2FolDragon%2Fp%2F7795174.html
[16]
https://youzan.github.io/vant/#/zh-CN/button: https://link.juejin.cn?target=https%3A%2F%2Fyouzan.github.io%2Fvant%2F%23% 2Fzh-CN%2Fкнопка
Добавить Автора
https://github.com/suoyuesmile/suo-blog/blob/master/articals/h5/0003.md