Как фронтенд-инженер, разработавший несколько проектов 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, bottomleft 的环境变量组成,这样可以安全地放入内容,而不会有被非矩形的显示切断的风险。对于矩形视口,例如普通的笔记本电脑显示器,其值等于零。 对于非矩形显示器(如圆形表盘,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小组 提供方法

  1. 注册方法 `bridge.register`
bridge.register('enterApp'function() {
  broadcast.emit('ENTER_APP')
})
复制代码
  1. 回调方法 `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 个步骤

  1. 安装插件\(全局安装\)
sudo npm install spy-debugger -g
复制代码
  1. 手机与电脑置于同一 wifi 下,手机设置代理

设置手机的 HTTP 代理,代理 IP 地址设置为 PC 的 IP 地址,端口为spy-debugger的启动端口

spy-debugger 默认端口:9888

Android :设置 - WLAN - 长按选中网络 - 修改网络 - 高级 - 代理设置 - 手动

IOS :设置 - Wi-Fi - 选中网络, 点击感叹号, HTTP 代理手动

  1. 手机打开浏览器或者 app 中 H5 页面
  2. 打开桌面日志网站进行调试,点击 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]

参考资料

[1]

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