XXHolic

XXHolic 查看完整档案

深圳编辑  |  填写毕业院校  |  填写所在公司/组织 github.com/XXHolic 编辑
编辑

个人动态

XXHolic 发布了文章 · 2020-03-13

CSS Flexible Box Layout

引子

最近接触 flex 布局的时候,碰到一些问题,于是借着这个机会,对 flex 相关的知识点进行整理。

简介

CSS 2.1 定义了 4 种布局模式,这些算法是根据盒子与它们的兄弟和祖先盒子的关系,来确定盒子自身大小和位置。分别有:

  • block 布局,为文档布局设计
  • inline 布局,为文本布局设计
  • table 布局,为表格形式的 2D 数据布局设置
  • positioned 布局,设计用于非常明确的定位,不用考虑文档中的其他元素

新引入的 flex 布局,跟 block 布局很类似,它缺少很多在 block 布局中,有关文本和文档相关的特性,例如浮动。相应的,它拥有了简单且强大的工具,用于来分配空间,并可以按照 web 应用程序和复杂网页经常需要的方式对齐内容。

目前处于候选推荐阶段,主流浏览器支持的情况很不错,详细见 Can I use flex

相关概念和术语

一个元素设置 display 属性值为 flexinline-flex,就会变成一个 flex 容器(flex container),其直接子元素被称为 flex 项(flex items),它们布局使用 flex 布局模式。

在 CSS 中定义了一些跟物理方向和空间相对应的一些概念,这些概念为未来定义新的布局提供理论支持,在 flex 布局模式中对应物理方向和空间的概念如下图。

32-css-flex

  • main axis:主轴,flex 项的排列是按照主轴进行排列,主轴的方向取决于 flex-direction 属性,不一定是水平方向。
  • main-start/main-end:flex 容器主轴上的开始/结束位置,flex 项的排列是从 main-start 开始,到 main-end 结束。
  • main-size:在主轴方向 flex 容器或者 flex 项的高度或宽度,它可能是元素的 widthheight 属性。类似的,它的 min/max main size 属性取决于它的 min-width/max-width 或者 min-height/max-height 属性。
  • cross axis:侧轴,跟主轴方向垂直的轴。
  • cross-start/cross-end:flex 容器侧轴上的开始/结束位置,flex 项的排列是从 cross-start 开始,到 cross-end 结束。
  • cross-size:在侧轴方向 flex 容器或者 flex 项的高度或宽度,它可能是元素的 widthheight 属性。类似的,它的 min/max cross size 属性取决于它的 min-width/max-width 或者 min-height/max-height 属性。

flex container

成为 flex 容器的方式是,设置 display 属性值为 flex | inline-flex

  • flex:当一个块级元素放在流布局中,这个值会让这个元素生成一个 flex 容器盒子。
  • inline-flex:当一个内联元素放在流布局中,这个值会让这个元素生成一个 flex 容器盒子。

flex 容器会为它的内容建立一个 flex 格式化上下文,它形成的包含块就像块级容器做的那样。flex 容器的外边距(margin)不会跟它内容的边距重合。overflow 属性适用于 flex 容器。flex 容器不是块级容器,并且有些适用于 block 布局的属性在 flex 布局中并不适用,特别是:

  • floatclear 不会产生浮动或者清空 flex 项,并且不会让元素脱离文档流。
  • vertical-align 对 flex 项没有作用。
  • ::first-linefirst-letter 伪元素不适用于 flex 容器,flex 容器不会为它们的祖先提供第一行格式化或第一个字母。

这是测试示例,移动端查看如下。

32-qrcode-flex-test

如果一个元素的 display 属性设置为 inline-flex,在特定的环境下,它的的 display 属性会被计算为 flex

用于 flex 容器的相关属性有:

  • flex-direction
  • flex-wrap
  • flex-flow
  • justify-content
  • align-items
  • align-content

flex-direction

属性名:flex-direction

可取值:row | row-reverse | column | column-reverse

默认值:row

适用于:flex 容器

可继承:否

flex-direction 属性通过设置 flex 容器主轴的方向,来指定 flex 项在 flex 容器中的放置方式。

这是测试示例,移动端查看如下。

32-qrcode-flex-direction

flex-wrap

属性名:flex-wrap

可取值:nowrap | wrap | wrap-reverse

默认值:nowrap

适用于:flex 容器

可继承:否

flex-wrap 属性决定 flex 容器是单行还是多行,侧轴的方向决定了新行的插入方向。

  • nowrap:flex 容器的子元素单行显示。
  • wrap:flex 项在当前行显示不了时,会换行显示。
  • wrap-reverse:flex 项在当前行显示不了时,会换行显示。

当值为非 wrap-reverse 时,cross-start 的方向与 当前 writing modeinline-startblock-start 方向一致,当值为 wrap-reverse 时,cross-startcross-end 方向相反。

这是测试示例,移动端查看如下。

32-qrcode-flex-wrap

flex-flow

flow-flowflex-directionflex-wrap 的缩写。
属性名:flex-flow

可取值:flex-direction || flex-wrap

适用于:flex 容器

可继承:否

justify-content

属性名:justify-content

可取值:flex-start | flex-end | center | space-between | space-around

默认值:flex-start

适用于:flex 容器

可继承:否

justify-content 属性沿着 flex 容器当前行的主轴对齐 flex 项。

  • flex-start:flex 项向行的开始位置放置。行内第一个 flex 项的 main-start 外边距边界与行的 main-start 的外边距边界齐平,每个后续的 flex 项与前一项齐平放置。
  • flex-end:flex 项向行的结束位置放置。行内最后一个 flex 项的 main-end 外边距边界与行的 main-end 的外边距边界齐平,每个前面的 flex 项与后续的项齐平放置。
  • center:flex 项向行的中间位置放置。行内每个 flex 项齐平放置并与行的中心对齐,行的 main-start 边界与第一个 flex 项之间的空间,和行的main-end 边界与最后一个 flex 项之间的空间等量。
  • space-between:flex 项在行内均匀分布。如果剩下的自由空间是负的,或者只有一个 flex 项在行内,这个值的作用与 flex-start 等同。除此之外,行内第一个 flex 项的 main-start 外边距边界与行的 main-start 的外边距边界对齐,行内最后一个 flex 项的 main-end 外边距边界与行的 main-end 的外边距边界对齐,剩下的 flex 项在行上均匀分布,任意两个相邻 flex 项的间距相同。
  • space-around:flex 项在行内均匀分布,两端各有一半的空间。如果剩下的自由空间是负的,或者只有一个 flex 项在行内,这个值会被识别为 center。除此之外,在行内任意两个相邻的 flex 项的间距相同,第一个和最后一个 flex 项与 flex 容器边界的间距是相邻 flex 项之间间距的一半。

32-justify-content

这是测试示例,移动端查看如下。

32-qrcode-justify-content

align-items

属性名:align-items

可取值:flex-start | flex-end | center | baseline | stretch

默认值:stretch

适用于:flex 容器

可继承:否

align-items 设置所有 flex 容器的项的默认对齐方式,包括匿名的 flex 项。

  • flex-start:flex 项的 cross-start 外边距边界与行的 cross-start 边界对齐。
  • flex-end:flex 项的 cross-end 外边距边界与行的 cross-end 边界对齐。
  • center:flex 项的外边距盒子在行的侧轴上中心。
  • baseline:flex 项基于基线对齐,行内所有参与的 flex 项目都基于基线对齐,并且拥有基线与其自身的 cross-start 外边距边界最大距离的 flex 项,与行的 cross-start 边界齐平。如果项目在必要的轴中没有基线,则从 flex 项的边框盒子合成基线。
  • stretch:如果 flex 项的 cross size 属性计算为 auto,并且侧轴的的外边距都不是 auto,那么 flex 项会被拉伸。它使用的值是尽可能的让 项的外边距盒子的 cross size 与行的大小一致的长度,仍然受到 min-height/min-width/max-height/max-width 的约束。

32-align-items

这是测试示例,移动端查看如下。

32-qrcode-align-items

align-content

属性名:align-content

可取值:flex-start | flex-end | center | space-between | space-around | stretch

默认值:stretch

适用于:多行 flex 容器

可继承:否

当在侧轴上有多余的空间时,align-content 属性让 flex 容器内的行对齐。注意,该属性对单行 flex 容器没有作用。

  • flex-start:行向 flex 容器的开始位置放置。flex 容器内第一行的 cross-start 边界与flex 容器的 cross-start 边界齐平,后续的每行与前一行齐平。
  • flex-end:行向 flex 容器的结束位置放置。flex 容器内最后一行的 cross-end 边界与flex 容器的 cross-end 边界齐平,每个前面的行与后续的行齐平放置。
  • center:行向 flex 容器的中间位置放置。flex 容器内每一行都齐平放置并与 flex 容器的中心对齐,flex 容器内第一行与 flex 容器的 cross-start 内容边界的空间,和 flex 容器内最后一行与 flex 容器的 cross-end 内容边界的空间等量。
  • space-between:行在 flex 容器内均匀分布。如果剩下的自由空间是负的,或者只有一行在 flex 容器内,这个值的作用与 flex-start 等同。除此之外,flex 容器内第一行的 cross-start 边界与 flex 容器的 cross-start 内容边界对齐,flex 容器内最后一行的 main-end 边界与 flex 容器的 main-end 的内容边界对齐,剩下的行在 flex 容器内均匀分布,任意相邻行的间距相同。
  • space-round:行在 flex 容器内均匀分布,两端各有一半的空间。如果剩下的自由空间是负的,这个值的作用与 center 等同。除此之外,flex 容器内任意相邻的行的间距相同,第一行和最后一行与 flex 容器边界的间距是相邻行间距的一半。
  • stretch:行会伸展占据剩余的空间。如果剩下的自由空间是负的,这个值的作用与 flex-start 等同。除此之外,剩余的空间会被所有的行平分。

32-align-content

这是测试示例,移动端查看如下。

32-qrcode-align-content

flex item

flex 容器下每个在文档流里的后代都会成为一个 flex 项。每个连续的子文本都包含在匿名块容器 flex 项中。如果只包含空白符,就不会渲染,就好像文本节点拥有属性 display: none 。flex 项为它的内容建立一个独立的格式化上下文。然而,flex 项是 flex 级别盒子,不是 block 级别盒子,它们参与自己的 flex 格式化上下文,不是在 block 格式化上下文。

如果在 flex 容器内,在文档流的后代设置 display 为内联的值,其计算值与块级别值等效。也就是说这种情况下 flex 项的 display 属性值被锁定。详细见 CSS Display

flex 容器内绝对定位,脱离文档流的后代不会参与 flex 布局。这样的 flex 容器的静态位置矩形(static-position rectangle )是它的内容盒子,这个静态位置矩形是用来决定绝对定位盒子偏移位置的容器。

相邻 flex 项的边距(margin)不会合并。flex 项的 marginpadding 为百分比时,跟块级盒子一样,会根据包含快的逻辑宽度计算。margin 的值为 auto 时,会在相应的维度,吸收额外的空间。它们可以用来对齐或者分开 flex 项。

用于 flex 项的相关属性有:

  • flex
  • order
  • align-self

flex

属性名:flex

可取值:none | [ <‘flex-grow’> <‘flex-shrink’>? || <‘flex-basis’> ]

默认值:0 1 auto

适用于:flex 项

可继承:否

影响 flex 项长度有 flex 因子(增长和收缩)和 flex 基础。当一个盒子是 flex 项时,flex 属性决定了盒子的主要大小。如果一个盒子不是 flex 项,flex 属性将不会有作用。

flex 设置值为 none 时,相当于 0 0 auto

flex-grow

值为数字。当空间剩余时,这个属性决定了 flex 项相对于 flex 容器下其它 flex 项,会增长多少空间。当在 flex 简写中省略时,设置为 1 。

flex-shrink

值为数字。当空间不足时,这个属性决定了 flex 项相对于 flex 容器下其它 flex 项,会收缩多少空间。当在 flex 简写中省略时,设置为 1 。

flex-basis

flex-basis 定义了在分配多余空间前,flex 项的初始 main size。

  • auto:使用该值时,将会取 main size 属性的值,如果 main size 属性的值也是 auto,则使用值 content
  • content:基于 flex 项的内容大小自动生成。
  • <‘width’>:除了上面两个值,flex-basis 使用方式与 widthheight 相同。

在 flex 简写中省略时,设置的值是 0 。

flex 常见值

  1. flex: initial:相当于 flex: 0 1 auto 。flex 项的大小基于 width/height 属性。当有剩余空间时,不会分配剩余空间,但允许空间不足时缩小到最小尺寸。对齐的属性或者 auto 外边距可以用来让 flex 项沿主轴对齐。
  2. flex: auto:相当于 flex: 1 1 auto 。flex 项的大小基于 width/height 属性。当有剩余空间时,会沿着主轴,尽可能的吸收剩余空间。如果所有项目都是 flex: autoflex: initialflex: none ,则在项目大小调整后,任何剩余空间将均匀分配到具有属性 flex: auto 的项。
  3. flex: none:相当于 flex: 0 0 auto 。flex 项的大小根据 width/height 属性。盒子不会变得有弹性,跟取值 initial 有点类似,但不允许 flex 项收缩,即使在溢出的情况下。
  4. flex: <positive-number>:相当于 flex: <positive-number> 1 0 。flex 项接收 flex 容器中指定比例的可用空间。如果 flex 容器下所有 flex 项都使用这种模式,它们的大小将与指定的弹性系数成比例。

这是测试示例,移动端查看如下。

32-qrcode-flex

order

属性名:order

可取值:<integer>

默认值:0

适用于:flex 项

可继承:否

order 属性控制了 flex 项在 flex 容器里面显示的顺序。flex 容器放置内容,从最低编号开始按顺序向上。序号相同的组,会按照它们在源文档中出现的顺序放置。flex 容器内绝对定位的后代,会被当做拥有 order: 0 ,这样做是为了决定它们相对于 flex 项的绘制顺序。

这是测试示例,移动端查看如下。

32-qrcode-order

align-self

属性名:align-self

可取值:auto | flex-start | flex-end | center | baseline | stretch

默认值:auto

适用于:flex 项

可继承:否

align-self 作用跟 align-items 一样,但它适用于 flex 项,可以为单独的 flex 项设置对齐的方式,会覆盖 align-items 的效果。

如果 flex 项的侧轴任一外边距为 auto ,那么 align-self 无效。

align-selfalign-items 多了一个可取值 auto ,使用该值时,对齐方式的控制权交给了父级盒子。

这是测试示例,移动端查看如下。

32-qrcode-align-self

参考资料

查看原文

赞 0 收藏 0 评论 0

XXHolic 发布了文章 · 2020-03-06

性能指标

引子

Performance Guide 中介绍了以用户为中心的指导模型,接着看下在实际测量中的一些性能指标。原文 User-centric performance metrics 前段时间还在 Web Fundamentals 下,今天去看的时候变成了外链,但中文版还在 Web Fundamentals 下。以下是个人理解的部分翻译。

翻译时 User-centric performance metrics 原文 Last updated: Nov 8, 2019 。

正文

我们都听过性能有多重要。但当我们谈到性能和让网站“快速”时,我们具体指的是什么?

事实上性能是相对的:

  • 一个站点对于一个用户(在一个有强力设备的快速网络上)来说可能是快速,但对于另外一个用户(在一个低端设备的慢速网络上)来说可能是慢速。
  • 两个站点可能在完全相同的时间内完成加载,但其中一个似乎加载的更快(如果它逐步加载内容,而不是等到最后才显示任何内容)。
  • 一个站点可能看起来加载很快,但之后对于用户的交互响应慢(或者根本没有响应)。

因此当谈到性能时,用精确并可以量化的客观标准来对其进行衡量,是很重要的。这些标准称为指标。

但是,仅仅因为一个指标是基于客观标准,并且可以定量测量,这并不一定意味着这些测量是有用的。

Defining metrics(定义指标)

在历史上,web 性能早期是通过 load 事件衡量。然而,尽管 load 在页面生命周期中,是一个定义良好的时刻,但这个时刻并不一定与用户关心的任何事情相对应。

例如,服务器可以用一个最小的页面来响应,该页面会立即触发 load 事件,但随后会异步获取页面中的所有内容并展示,这个过程可能是在 load 事件触发后好几秒发生。虽然这个页面在技术上拥有一个很快的加载时间,但这个时间与用户实际体验页面加载的情况不符。

在过去的几年,Chrome 团队成员与 W3C Web Performance Working Group 合作,一直致力于标准化一组新的 API 和指标,它们更准确地衡量用户如何体验 Web 页面的性能。

为了帮助确保指标与用户相关,我们围绕几个关键问题对它们进行了界定:

问题描述
Is it happening?导航是否成功启动?服务器响应了吗?
Is it useful?有足够的内容提供给用户使用吗?
Is it usable?用户可以跟页面交互吗,还是繁忙?
Is it delightful?交互是否顺畅自然,没有滞后和干扰?

How metrics are measured(如何测量指标)

测量性能标准通常有以下方式:

  • In the lab:使用工具在一致的受控环境中模拟页面加载
  • In the field:真实用户实际加载和与页面交互。

这两个选项没有那一种一定比另一个更好或更差,事实上,为了确保良好的性能,这两种方式你通常都想使用。

In the lab(在实验室)

当开发新的功能,在实验室测试性能是有必要的。在产品发布新特性之前,不可能在实际用户身上测量它们的性能特性,因此在特性发布之前在实验室测试它们,是防止性能回归的最佳方法。

In the field(在现场)

另一方面,虽然实验室测试性能是一个合理方式,但它不一定反映所有用户在野外如何体验你的站点。

根据用户的设备功能和他们的网络情况,站点性能可能会有很大的变化。用户是否(如何)与页面交互同样会造成影响。

此外,页面加载可能是不确定的。例如,有加载个性化或广告的站点,用户之间可能会体验到截然不同的性能特性。实验室测试将不会捕捉到这些差异。

要真实的了解你的站点对于用户的性能表现如何,唯一的方法就是在用户加载和交互时测量它的性能。这种类型的测量通常称为 Real user monitoring ,简称 RUM

Types of metrics(指标类型)

还有一些其他类型的指标与用户如何感知性能相关。

  • Perceived load speed :一个页面加载并将其所有可视元素呈现到屏幕的速度。
  • Load responsiveness :一个页面加载和执行任何必需的 JavaScript 代码的速度,以便组件快速响应用户交互
  • Runtime responsiveness :页面加载后,页面响应用户交互的速度。
  • Visual stability :页面上的元素是否以用户不期望的方式移动,并可能干扰他们的交互?
  • Smoothness :转换和动画是否以一致的帧速率进行渲染,并从一个状态流畅地转变到下一个状态?

从以上提供的几种类型的性能指标中,可以清晰的知道,没有单一的指标能够捕获页面所有的性能特征。

Important metrics to measure(关键指标)

  • First contentful paint (FCP) :测量页面从开始加载到页面任何一部分内容渲染到屏幕上的时间。(lab,field)
  • Largest contentful paint (LCP) :测量页面从开始加载到最大文本块或图像元素渲染到屏幕上的时间。(lab,field)
  • First input delay (FID) :测量用户第一次与你的站点交互到浏览器实际可以响应该交互的时间。(field)
  • Time to Interactive (TTI) :测量从页面开始加载到可视化呈现、其初始化脚本(如果有的话)已加载,以及能够可靠快速地响应用户输入的时间。(lab)
  • Total blocking time (TBT) :测量 FCPTTI 之间,长时间阻塞主线程导致阻碍输入响应的总时间。(lab)
  • Cumulative layout shift (CLS) :测量从页面开始加载到其生命周期状态变为 Hidden 之间发生的所有非预期布局移动的累积分数。(lab,field)

虽然此列表包含与用户相关许多不同方面的衡量标准,但并不包括所有方面(例如,当前未涵盖运行时响应性和平滑性)。

在某些情况下,将引入新的指标来弥补缺失的部分,但在其它情况下,最好的指标是专门为你的站点定制的。

Custom metrics(自定义指标)

上面列出的性能指标,有助于整体了解 web 上大多数站点的性能特征。它们还可以为站点提供一组通用的指标,以便将其性能与竞争对手进行比较。

然而,有时候在一些方面比较独特的站点,需要额外的指标来捕获性能全景图。例如,在 Largest contentful paint (LCP) 中,有可能最大的元素不是这个页面主要内容的一部分,因此 LCP 可能不相关。

为了应对这样的情况,Web Performance Working Group 还标准化了较低级别的 API,这些 API 对于实现自定义度量很有用:

可以在 Custom Metrics 中查看更详细的介绍。

参考资料

查看原文

赞 0 收藏 0 评论 0

XXHolic 发布了文章 · 2020-02-29

碰撞检测

引子

碰撞检测(Collision Detection)这个东西,之前找 canvas 相关资料的时候碰到过,有些好奇,就去尝试了解一下。

简介

与碰撞检测有些类似的一个概念:命中检测(Hit-testing),也被广泛运用。

在计算机图形编程中,命中检测是确定用户控制的光标(或触摸屏上的触摸点)是否与屏幕上绘制的给定图形对象(如形状)产生相交的过程。根据这个定义,web 开发中碰到鼠标点击下拉菜单等这些交互都属于这个范畴。

碰撞检测用于检测多个不同图形对象的交叉点,不是光标与一个或多个图像对象的交叉点。在 web 游戏中也涉及到这些,2D 游戏中的碰撞检测算法,依赖于碰撞物体的形状(例如矩形与矩形、矩形与圆形、圆形与圆形)。基于 canvas,下面看看几种简单的情景。

光标碰撞检测

使用 canvas 创建交互时,在画布内点击,如果需要判断点击所在的图形,就需要做碰撞检测。思路比较简单,就是要检测光标点击的坐标是否在某个形状里面。在测试中发现几点需要注意:

  • 遍历形状的顺序,因为绘制的图形可能重叠,后绘制的一般在上层,这个时候倒序遍历才合理。
  • 获取点击坐标时,注意移动端和 PC 端对应属性,移动端 touch 属性中可能有多个坐标。
  • 图形的形状,规则图形和非规则图形判断的方式有差异。

这是简单的示例,移动端访问如下:

46-cursor

基于像素颜色碰撞检测

上面数学计算的方式,如果在一些图案信息较多的情况下,需要存储大量的数据,会非常的不方便。另外的一种方式就是取得像素块,检测它们的颜色。例如这个示例,移动端访问如下:

46-pixel

示例中矩形区域是图片,其中线条颜色都是黑色,实现的思路是在小球移动之前,先取包含了小球且比小球稍大的空间像素,遍历像素数据,判断是否有黑色且不透明,如果有就说明在下一次重绘时就会发生碰撞。在迷宫类的游戏实现中,这种方式就会很方便,见小游戏 Maze

在测试中发现几点需要注意:

  • 选择图片时,需要注意图片中颜色的使用,因为要用来作为判断依据,色值不要随意混用。
  • 使用 getImageData 方法时,获取数据的位置是根据 canvas 实际的像素数据,而不是渲染显示的大小。

矩形与矩形碰撞检测

这里是指轴对齐(意味着没有旋转)的两个矩形碰撞检测。下面的一个算法可以用来进行判断:

var rect1 = {x: 5, y: 5, width: 50, height: 50}
var rect2 = {x: 20, y: 10, width: 10, height: 10}

if (rect1.x <= rect2.x + rect2.width &&
   rect1.x + rect1.width >= rect2.x &&
   rect1.y <= rect2.y + rect2.height &&
   rect1.height + rect1.y >= rect2.y) {
    // collision detected!
}

上面的条件,以刚好碰撞的状态做对照,思考起来会比较方便理解。这是简单的示例,移动端访问如下:

46-rect

圆形与圆形碰撞检测

这种形式的碰撞检测,通过两个圆心之间的直线距离,与两个圆的半径之和作比较。

var circle1 = {radius: 20, x: 5, y: 5};
var circle2 = {radius: 12, x: 10, y: 5};

var dx = circle1.x - circle2.x;
var dy = circle1.y - circle2.y;
var distance = Math.sqrt(dx * dx + dy * dy);

if (distance <= circle1.radius + circle2.radius) {
    // collision detected!
}

这是简单示例,移动端访问如下:

46-circle

从上面的示例中,发现如果使用在动态的场景下,效果并不一定如预期。

参考资料

查看原文

赞 0 收藏 0 评论 0

XXHolic 发布了文章 · 2020-02-21

性能优化模型

引子

About Performance 的最后提供了一些关注的点,但感觉比较零散,有没有理论体系化的思想作为依据,通过查找资料,发现了两种指导方式:RAIL ModelPRPL Pattern ,下面是带有个人理解的部分翻译。

翻译时 RAIL Model 原文 Last updated 2019-02-12 ,PRPL Pattern 原文 Last updated: Nov 5, 2018 。

RAIL Model

RAIL 是一个以用户为中心的性能模型,它将用户的体验分解为关键的操作。RAIL 的目标和方针旨在帮助开发人员和设计人员确保每一个操作都有良好的用户体验。通过设计一个考虑性能的结构,RAIL 使设计人员和开发人员能够有效的关注影响用户体验最大的工作。

每个 web 应用程序的生命周期都有四个不同的方面,性能以不同的方式适合它们:

48-rail

目标和方针

在 RAIL 的上下文中,目标(goals)和方针(guidelines)有特殊的含义:

  • 目标:用户体验相关的关键性能指标。因为人类的感知是相对稳定的,所以这些目标不太可能很快改变。
  • 方针:帮助你实现目标的建议。这些可能是特定于当前的硬件和网络连接条件,因此可能随着时间发生改变。

关注用户

让用户成为你的性能工作的焦点。下表描述了用户如何感知性能延迟的关键指标:

延迟时间用户反应
0 - 16ms人们特别擅长跟踪运动,如果动画不流畅,他们就会对运动心生反感。 用户可以感知每秒渲染 60 帧的平滑动画转场。也就是每帧 16ms(包括浏览器将新帧绘制到屏幕上所需的时间),留给应用大约 10ms 的时间来生成一帧。
0 - 100ms在此时间窗口内响应用户操作,他们会觉得获取的结果是立即的。时间再长,操作与反应之间的联系就会中断。
100 - 300ms用户会体验到轻微的可感知延迟。
300 - 1000ms在此窗口内,延迟感觉像是一个连续任务自然进展的一部分。对于网络上的大多数用户,加载页面或更改视图代表着一个任务。
1000+ms超过 1s ,用户对正在执行的任务失去注意力。
10,000+ms用户感到失望,可能会放弃任务,之后他们或许不会再回来。

用户对性能延迟的感知不同,这取决于网络条件和硬件。例如,通过快速 Wi-Fi 连接在功能强大的台式机上,1000ms 内的加载体验是合理的,因此用户已经习惯了 1000ms 的加载体验。但低于 3G 连接速度较慢的移动设备来说,5000ms 内的加载体验是一个更实际的目标,因此移动用户通常更耐心。

Response:在 50ms 内处理事件

目标

在 100ms 内完成由用户输入启动的过度。用户花费大部分时间是在等待站点响应他们的输入,而不是等待站点加载。

方针

  • 在 50ms 内处理用户输入事件,是为了保证 100ms 内有可见的响应,否则动作与反应的联系就会断开。这适用于大多数输入,例如单击按钮、切换表单控件或启动动画。这不适用于触摸拖动或滚动。
  • 尽管这听起来可能有违直觉,但对用户输入做出立即响应并不总是正确的。你可以用这个 100ms 的窗口做其它昂贵的工作。但要注意不要阻塞用户,如果可能的话,在后台工作。
  • 对于需要超过 50ms 才能完成的操作,始终提供反馈。

50ms 还是 100ms ?

目标是在 100ms 内响应输入,那么为什么我们的预算只有 50ms ?这是因为除了输入处理之外,通常还有其它的工作正在处理,而这部分工作占据了可接受输入响应的部分时间。如果应用程序在空闲时间以建议的 50ms 执行工作,这意味着如果在其中一个工作块期间发生输入,则输入的排列等候可以长达 50ms。根据这一点,假设只有剩余 50ms 可用于实际的输入处理是安全的。此效果在下图中可见,显示了在空闲任务队列中,输入是如何接受的,从而缩短了可用的处理时间:

48-rail-response-details

Animation:10ms 内生成一帧

目标

  • 在10ms 或更短的时间内生成动画中的每一帧。技术上,每帧的最大预算是 16ms(1000ms / 60帧/s ≈ 16ms),但是浏览器需要大约 6ms 来渲染每个帧,因此每帧指导方针是 10ms 。
  • 以视觉平滑度为目标。当帧速率变化时,用户会注意到。

方针

  • 在像动画这样的高压点中,关键是不论能不能做,什么都不要做,做最少的工作。如果可能,利用 100ms 响应预先计算开销大的工作,这样你就可以尽可能增加实现 60fps 的可能性。
  • 查看 Rendering Performance 中动画优化策略。
  • 识别所有类型的动画。动画不仅仅是精致的 UI 效果。这些交互都被视为动画:

    • 视觉动画,例如进入、退出、补间和加载提示。
    • 滚动,包括抛掷,也就是用户开始滚动,然后放开,页面继续滚动。
    • 拖拽,动画通常遵循用户交互,如平移地图或捏缩放。

Idle:最大化空闲时间

目标

最大化空闲时间以增加页面在 50ms 内响应用户输入的可能性。

方针

  • 利用空闲时间完成延迟的工作。例如,对于初始页面加载,加载尽可能少的数据,然后使用空闲时间加载其余的数据。
  • 在 50ms 或更短的空闲时间内执行工作。再长一点,你就有可能干扰应用程序在 50ms 内响应用户输入的能力。
  • 如果用户在空闲时间工作期间与页面交互,则用户交互应始终具有最高优先级并中断空闲时间工作。

Load:传送内容并在 5s 内具备可交互性

当页面加载缓慢时,用户的注意力就会转移,用户会认为任务已经中断。快速加载的站点具有更长的平均会话时间、更低的跳出率和更高的广告可视性。

目标

  • 快速加载性能的优化与用户用于访问站点的设备和网络功能相关。目前,一个好的目标是,第一次加载的页面,在 5s 或更短的时间内在3G连接速度较慢的中档移动设备上具备可交互性。见 Can You Afford It?: Real-world Web Performance Budgets 。但要注意这些目标可能会随着时间的推移而改变。
  • 对于后续加载,一个好的目标是在 2s 内加载页面。但这个目标也可能随着时间的推移而改变。

48-speed-metrics

方针

  • 在用户之间常见的移动设备和网络连接上测试加载性能。如果您的企业有关于用户使用的设备和网络连接的信息,则可以使用该组合并设置自己的加载性能目标。否则,可以参考 The Mobile Economy 中的年度数据,选择合适的目标。
  • 请记住,尽管你的典型移动用户可能声称它的设备是在 2G、3G 或 4G 连接上,但实际上,由于数据包丢失和网络变化,有效连接速度通常要慢得多。
  • 专注于优化 Critical Rendering Path
  • 没有必要在 5 秒内加载所有的东西,以产生一种完整加载的感觉。启用渐进式渲染并在后台执行某些操作。将非必要加载推迟到空闲时间段。
  • 识别影响页面加载性能的因素:

    • 网络速度和延迟
    • 硬件(例如 CPU 速度慢)
    • 缓存回收
    • 二级/三级缓存的差异
    • JavaScript 解析

PRPL Pattern

PRPL 描述了一种模式,用于使网页更快的加载并变得可交互:

  • Push(或者 preload)最重要的资源。
  • 尽快渲染(Render)初始路径。
  • 预缓存(Pre-cache)剩余资源.
  • 延迟加载(Lazy load)其他路由和非关键资源。

Apply instant loading with the PRPL pattern 中主要结合了一种工具进行讲解,偏重实际操作,这里就不多介绍了。

参考资料

查看原文

赞 0 收藏 0 评论 0

XXHolic 发布了文章 · 2020-02-14

性能的影响

引子

关于性能,大家都知道重要,那为什么重要?只能想起一些零碎的答案,于是就去找资料,发现 Why Performance Matters 这篇文章,比较符合自己想要的答案。也有中文版本,但个人觉得里面的一些信息多余,于是按照个人的偏好重新组织和翻译部分,期间发现其中有的链接已无效,进行了补充。

翻译时原文版本 Last updated 2019-09-16 。

性能影响用户

对于用户的影响有两个方面:

  • 用户的去留
  • 用户使用成本

用户的去留

我们希望用户能够与我们构建的内容进行有意义的互动。在任何成功的在线业务中,性能扮演着一个重要的角色,下面的案例显示出高性能网站有利于保留用户:

下面的案例研究,显示出低性能对于业务目标有负面影响:

用户使用成本

低性能的网站和应用,可能导致用户产生更多的实际成本。

随着移动用户在全球互联网用户中所占比例不断扩大,必须要注意的是,这些用户中许多人通过 LTE、4G、3G 甚至 2G 网络上网。Ben Schwarz 的实际性能研究中指出,预付费的数据流量成本正在下降,这将使得曾经需要支付高额费用才能实现的网络访问变得更加便宜。移动设备和上网不再是奢侈消费。在日益互连的世界中,它们成为进行导航和日常活动必需的常用工具。

自 2011 起,页面总大小就开始稳步增加,而这一趋势似乎将持续下去。随着典型页面发送的数据增多,用户必须经常为其按流量计费的数据流量续费,如此一来,用户的成本便会增加。

除了为用户省钱,速度快、负荷轻的用户体验对处在危机中的用户同样至关重要。 医院、诊所和危机中心等拥有的各类公共在线资源,为用户提供面临危机时需要的特定重要信息。虽然紧急情况下设计对于高效展示重要信息至关重要,但快速传达这类信息的重要性同样不可低估。

性能影响转换率

留住用户对于提升转化率至关重要。响应速度慢会对网站收入带来不利影响,反之亦然。 以下是一些示例,显示了性能在提升业务收入方面扮演怎样的角色:

性能影响体验

当导航到某个网址时,可能是从不同的入口进入,根据诸多条件的不同,例如使用的设备和网络情况,用户体验可能各不相同。下图是同一个页面慢连接加载和快连接加载的对比。

47-speed-comparison

当网站开始加载时,用户需要等待一段时间才能看到要显示的内容,在此之前就谈不上用户体验。快速连接会让这段时间一闪而过,而如果连接速度较慢,用户就不得不等待。页面资源加载较慢时,用户可能会遇到更多问题。

性能是创造良好用户体验的基本要素。当网站传输大量代码时,浏览器必须使用用户数兆字节的数据流量才能下载相应代码。移动设备的 CPU 计算能力和内存有限,一些未经优化的代码,可能我们认为并没有多大,但却常使移动设备崩溃。这会导致性能低,进而导致无响应。我们深知人类的行为,用户忍受低性能的应用到一定程度后,必定会将其抛弃。

性能影响搜索排名

在谷歌搜索排名中,页面性能也是一个排名的因素。

接着该做什么

可以从三个方面进行关注:

  • 关注发送的资源
  • 关注发送资源的方式
  • 关注发送的数据量

关注发送的资源

有下面的几点建议:

  • 如果使用了 UI 类框架,先问问自己是否有必要。
  • JavaScript 库非常方便,但不一定必需。
  • 并非所有网站都需要做成单页面应用 (SPA),此类应用通常广泛使用 JavaScript。这类资源必须经过下载、解析、编译和执行,是网页上昂贵的资源。

关注发送资源的方式

高效的传输对于构建快速用户体验至关重要。

  • 迁移至 HTTP/2。
  • 使用资源提示尽早下载资源。
  • 平均而言,现代网站传输大量 JavaScript 和 CSS。 在 HTTP/1 环境中,常见的做法是将样式和脚本捆绑成较大软件包。 这么做是因为大量请求会对性能带来不利影响。 使用 HTTP/2 后就不需要再这么做,因为同时发送多个请求的成本更低。 考虑使用 webpack 中的代码拆分来限制仅下载当前页面或视图需要的脚本数。 将 CSS 拆分为较小的模板或组件专用文件,且仅在可能使用的地方纳入这些资源。

关注发送的数据量

有下面的一些建议:

  • 压缩资源。
  • 服务器配置资源压缩。
  • 图像优化, WebP 受到广泛的浏览器支持,不但能保持高视觉质量,而且使用的数据比 JPEG 和 PNG 少。 JPEG XR 是另一种替代格式,受 IE 和 Edge 的支持,可提供类似的空间节省。
  • 使用视频取代动画 GIF。 动画 GIF 会占用大量空间。 同等画质的视频则小得多,通常低 80% 左右。 如果您的网站大量使用动画 GIF,这可能是提升加载性能最有效的方法。
  • Client hints 可以根据当前网络条件和设备特性定制资源交付。 DPRWidthViewport-Width 标头可以帮助您使用服务器端代码提供适合设备的最佳图像并提供更少标记。 Save-Data 标头可以帮助您为有特定需求的用户提供负荷更轻的应用体验。
  • NetworkInformationAPI 可显示有关用户网络连接的信息。 此类信息可用于调整网速较慢用户的应用体验。

参考资料

查看原文

赞 0 收藏 0 评论 0

XXHolic 发布了文章 · 2020-02-07

Canvas 橡皮擦效果

引子

解决了第一个问题图像灰度处理之后,接着就是做擦除的效果。

思路

一开始想到 Canvas 的画布可以相互覆盖的特性,彩色原图作为背景,灰度图渲染到 Canvas 画布上,然后手指滑动的时候,把接触的部分清除掉,就显示出了背景图。关键的部分是怎么清除画布上已有图像,查询资料发现有两种方式:

  1. 使用 clearRect 方法,会把指定范围所有像素变成透明,并擦除之前绘制的所有内容。
  2. 使用 globalCompositeOperation 属性,该属性设置在绘制新形状时应用合成操作的类型,值为 destination-out 时效果如下:

66-destination-out

在上面两种方式中,第一种方式正是功能实现所需要的,但擦除时的边角效果没有第二种方式理想。下面以第二种方式作为示例。

实现

在实现的过程中,发现有几点需要注意:

  • 手机端高清显示处理。
  • 注意 globalCompositeOperation 属性设置的时机,过早设置,可能画布上无法显示内容。
  • 擦除状态的控制,特别是在 PC 端,如果不设置相应状态,鼠标移动的时候就可能发生擦除。

这是示例页面,移动端访问如下:

66-normal

优化一

一般用户都不会全部进行擦除,而且这种体验也不好,所以擦除面积达到一定百分比时,自动擦除剩余的部分。

擦除效果实际上是改变了已有图像的透明度,可以通过 getImageData 方法查看 Canvas 上图像的像素数据。通过统计符合透明度要求的像素所占百分比,就等效擦除面积的百分比。至于透明度多少算擦除有效,看实际应用场景和所需效果。下面是主要的实现:

  /**
   * 获取透明所占百分比,返回一个 <= 1 的值
   * @param {object} context canvas 上下文对象
   * @param {number} opacity 透明度参考值,初始参考透明值是 128
   */
  function getOpacityPercentage (context, opacity = 128) {
    var imageData = context.getImageData(0,0,248,415);
    var colorDataArr = imageData.data;
    var colorDataArrLen = colorDataArr.length;
    var eraseArea = [];
    // rgba 显示的模式,所以一个像素表示有 4 个分量,透明度是最后一个分量
    for(var i = 0; i < colorDataArrLen; i += 4) {
      // 严格上来说,判断像素点是否透明需要判断该像素点的 a 值是否等于0,
      if(colorDataArr[i + 3] < opacity) {
        eraseArea.push(colorDataArr[i + 3]);
      }
    }
    var divResult = eraseArea.length / (colorDataArrLen/4);
    // 处理除不尽的情况
    var pointIndex = String(divResult).indexOf('.');
    if (pointIndex>-1) {
      divResult = String(divResult).slice(0,pointIndex+5);
    }
    return Number(divResult).toFixed(2);

  }

这是示例页面,移动端访问如下:

66-percentage

优化二

优化一中擦除全部的效果是一下子就不见了,但我在那个游戏里面看到的擦除效果是有一个过程的。于是再折腾了一下。

这是示例页面,移动端访问如下:

66-progress

参考资料

查看原文

赞 0 收藏 0 评论 0

XXHolic 发布了文章 · 2020-01-31

h5 穿透滚动

引子

h5 页面有弹窗浮层时,浮层之下若产生了滚动,滑动浮层时会让其产生滚动。这是示例页面,移动端访问如下:

63-problem

原因

找到的信息里面有两种说法:

  • 使用了 -webkit-overflow-scrolling: touch ,另外这个不是标准属性。
  • 浮层也是页面的一个元素,浮层的展示正常,页面中的其它元素按照本来的方式展示运作。也就是说这是一个正常的现象,只不过是我们不想要这种效果。

针对第一种说法,进行测试验证,这是示例页面,移动端访问如下:

63-no-touch

发现:跟 -webkit-overflow-scrolling: touch 无关。

处理方法

在网上找到的资料,主要有两种思路:

  1. 阻止 touch 相关的事件。
  2. 弹出浮层时,禁止元素滚动,浮层消失时,恢复滚动。

第一种思路在很多资料中提到有明显的缺陷:

  • 弹出层的滚动会有问题。
  • 会锁死滚动区域。
  • 弹出层的事件处理可能会产生影响。

较多采用第二种思路,但也有对应的问题:

  • 元素滚动的状态切换,会丢失滚动的位置。

针对滚动位置丢失问题,采用动态记录滚动位置的方式可以解决。

示例代码

// 以下方法使用的前提是产生滚动元素为 body
function fixedEle() {
  var scrollEle = document.body;
  // 有可能出现浮层内切换的情况,已经设置了就不用重复设置了。
  if (scrollEle.style.position !== 'fixed') {
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
    scrollEle.style.cssText += 'position:fixed;top:-'+scrollTop+'px;';
  }
}

function recoverEle() {
  var scrollEle = document.body;
  var top = scrollEle.style.top;
  scrollEle.style.position = '';
  scrollEle.style.top = '';
  document.body.scrollTop = document.documentElement.scrollTop = -parseInt(top);
}

这是示例页面,移动端访问如下:

63-solution

参考资料

查看原文

赞 0 收藏 0 评论 0

XXHolic 关注了用户 · 2020-01-22

阿山 @a_shan

一只英语专业的程序猿

微信公众号:GitWeb

微信交流群:公众号内加好友(备注思否),拉你进群

关注 2894

XXHolic 赞了文章 · 2020-01-22

微信小程序wx.request请求数据报错:不在以下 request 合法域名列表中

首先写一个后台的数据接口,地址是:http://localhost/weicms/index...

然后使用wx.request调用后台数据接口的地址

示例代码

1 wx.request({  
2   url: 'http://localhost/weicms/index.php?s =/addon/Cms/Cms/getList',  
3   data: {  
4     x: '',  
5     y: ''  
6   },  
7   header: {  
8     'content-type': 'application/json'// 默认值  
9   },  
10  success (res) {  
11     console.log(res.data)  
12    }  
13  })

运行代码,效果如下图:

1.jpg

从上图中看到页面一片空白,没有获取到数据,并且控制台报错(request 合法域名校验出错;http://localhost 不在以下 request 合法域名列表中)

为何出现这种错误?

打开wx.request网络请求的开发文档可以看到

2.jpg

上面截图中红色框就是问题所在(小程序服务器域名配置中是不能使用IP地址跟localhost),示例代码中wx.request请求的url地址包含localhost,因此出错。

但是一般开发过程中都要先在本地开发调试。如果没法使用ip地址跟localhos,本地开发调试过程中如何获取数据呢,有没有办法在本地开发调试的时候屏蔽这个错误呢?

答案是有的。开发文档中指出了可以跳过域名校验,如下图:

3.png

具体在哪里开启不检验域名的选项呢?在微信开发者工具中,点击详情后,选中不检验合法域名,如下图所示:

4.jpg

此时,再次运行代码后,效果如下图:

5.jpg

从上图看到数据已经成功获取到了,且控制也没有报错,只是提示:配置中关闭合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书检查

查看原文

赞 70 收藏 2 评论 0

XXHolic 发布了文章 · 2020-01-22

Safari 导航栏

引子

最近在 iPhone 的 Safari 查看 h5 页面时,发现有些平台的页面向下滚动时,顶部地址栏和底部导航栏会自动收起,整个页面空间多了不少,可以看到更多信息,这种效果比较适合当前业务场景。之前都没怎么关注这个,查找了一些资料,尝试后总结一下。

隐藏 Safari 导航栏

当页面内容过多,向下滚动时,导航栏和地址栏收起的现象,在 Safari 是正常的特性,以下称为默认隐藏。这是一个纯显示页面,移动端访问如下:

61-pure-page

默认隐藏

通过查找资料和对比其它平台的页面,主要的思考方向是页面结构和 CSS 样式。

首先想到在项目里面常会引入一个重置样式的库 normalize.css,看下是否会影响这个效果。这是测试页面,移动端访问如下:

61-normalize-page

测试结果是:不会影响

然后就是对比页面结构,发现了下面几种情况:

  • 滚动容器非 body 元素,默认隐藏无效。这是示例页面,移动端访问如下:

61-no-body

  • 滚动容器为 body 元素,html 设置了样式 overflow: hidden ,默认隐藏无效。这是示例页面,移动端访问如下:

61-no-body

  • 滚动容器为 body 元素,html 默认样式,默认隐藏有效。这是示例页面,移动端访问如下:

61-no-body

一直隐藏

需要按照下面的步骤操作:

  1. 添加标签 <meta name="apple-mobile-web-app-capable" content="yes" /> ,意思是让应用以全屏的方式显示,详细见 Supported Meta Tags
  2. 用 iPhone 的 Safari 打开页面,使用“添加到主屏幕”。
  3. 回到主屏幕,点击对应图标进入。

这是示例页面,移动端访问如下:

61-full-screen

这种需要用户自己操作多步,推广很难。未找到一直能隐藏导航栏的 h5 示例页面。

显示 Safari 导航栏

在上面的尝试中,一直显示导航栏的情况有:

  1. 滚动容器非 body 元素。
  2. 滚动容器为 body 元素,html 设置了样式 overflow: hidden

iPhone 系统占比

随着时间推移,网上不少方法无效,有些是针对特定的系统,下面可以查看系统占比:

参考资料

查看原文

赞 1 收藏 0 评论 0

XXHolic 发布了文章 · 2020-01-17

粘性头部效果

引子

最近碰到一个效果:页面滚动时,当指定元素超出可视区时,需要固定在可视区顶部。后来想到另外一种方式,在此统一记录一下。

思路一

这种思路比较常见,很早就有在使用,具体是监听滚动事件,在处理事件程序中计算指定元素到可视区顶部的位置,超出可视区时更改元素 position 属性脱离原来的文档流。

这里需要注意的点有:

  • 元素脱离了文档流后,原本所占据的位置,需要处理,否则元素后面的内容会出现突然上移的现象。
  • 滚动事件频繁触发的处理,离开页面时记得要解除事件绑定。

这是示例页面,移动端访问如下:

60-qr-fixed

示例主要代码

<!doctype html>
<html lang="en">
  <head></head>
  <body>
    <div id="titleHolder"></div>
    <h2 class="title" id="title">需动态固定的元素</h2>
    <div>内容</div>
  </body>
</html>
  body {
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
  .title-holder,
  .title {
    top: 0;
    margin: 0;
    width: 100%;
    height: 50px;
    line-height: 50px;
    text-align: center;
    background-color: #fff;
  }
  var scrollObj = document.querySelector('body');
  var targetHolderEle = document.querySelector('#titleHolder');
  var targetEle = document.querySelector('#title');
  var scrollMark = null;
  function dealScroll() {
    if (scrollMark) {
      clearTimeout(scrollMark);
    }

    scrollMark = setTimeout(() => {
        var topDistance = 0;
        // getBoundingClientRect 有些浏览器不支持
        if (targetHolderEle.getBoundingClientRect) {
          var pos = targetHolderEle.getBoundingClientRect();
          topDistance = pos.top;
        } else {
          var eleTop = targetHolderEle.offsetTop;
          topDistance = eleTop - scrollObj.scrollTop;
        }
        if (topDistance < 1) {
          targetHolderEle.setAttribute('class','title-holder');
          targetEle.style.position = 'fixed';
        } else {
          targetHolderEle.setAttribute('class','');
          targetEle.style.position = 'static';
        }
    }, 10);
  }

  function listenScroll() {
    scrollObj.addEventListener('scroll', dealScroll);
  }

思路二

使用新的 positionsticky 。想到这个,是因为想起曾写过 position 属性值 sticky 这篇总结。这种方式相对减少了很多代码,兼容性的问题,看实际应用场景的要求。

这是示例页面,移动端访问如下:

60-qr-sticky

参考资料

查看原文

赞 1 收藏 0 评论 0

XXHolic 发布了文章 · 2020-01-10

浮层滚动问题

引子

使用 positiontransform 实现从右边滑出的 h5 浮层效果,但在手机浏览器上左右滑动时,页面产生了左右滚动条,浮层也出来了。这是问题页面,移动端访问如下:

59-qrcode-problem

这个现象不应该出现,问题好解决,但为什么会这样?想了一下,好像知道相关点,但又讲不清,所以在此梳理一下。

原因

产生了滚动,很自然的就会想到 overflow 属性,那么就先来了解一下。

overflow 属性指定了块容器元素的内容溢出时是否被剪裁,是 overflow-xoverflow-y 的简写。

Nameoverflow
可取值visible、hidden、scroll、auto、inherit
默认值visible
适用于块级容器和建立了格式化上下文的盒子
继承性
  • visible:这个值表示内容不会被剪切,可能会在盒子之外渲染。
  • hidden:这个值表示内容被剪切,并且不应提供滚动用户界面来查看剪裁区域之外的内容。
  • scroll:这个值表示内容被剪切,并且如果用户代理使用在屏幕上可见的滚动机制(例如滚动条或窗格),则无论盒子中的任何内容是否被剪裁,都应为盒子显示该机制。这是为了避免滚动条在一个动态环境中,出现和消失造成的任何问题。当指定这个值且目标媒介是打印时,溢出的内容可能会被打印,也可能不会打印。当用在 table boxes 时,这个值跟 visible 作用一致。
  • auto:这个值表示依赖用户代理,但应为溢出的盒子提供滚动机制。当用在 table boxes 时,这个值跟 visible 作用一致。

通过上面的了解,想先纠正一下取值为 auto 的一种认识:浏览器会自动根据内容决定是否产生滚动。从现象上看似乎是这样,但标准上不是这样的,只是建议。如果用户代理不提供滚动,那就没有,而且也不一定是滚动,这都依赖于用户代理。

再来分析一下上面有问题的情况,主要的结构和样式如下:

<!doctype html>
<html>
  <body>
    <div class="pop"></div>
  </body>
</html>
html {overflow-x: hidden;}
html,body { height: 100%; }
body { position: relative; }
.pop {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, .5);
  transform: translateX(100%);
}

body 没有显式设置 overflow 的值,所以默认是 visible ,由于浮层使用 transform 超出了可视区,那么先来确定一下 bodyoverflow 是否有效。

设置 body 宽度的效果页面,移动端访问如下:

59-qrcode-body

发现符合 overflow: visible 的效果,没有产生左右滚动条。

接着有了下面的猜想:只要超出了可视区就会产生滚动。在前面的基础上,设置浮层 transform 超出可视区,这是效果页面,移动端访问如下:

59-qrcode-scroll

结果跟猜想的一样,这也说明从一开始并不是 body 产生了滚动,而是 viewport 产生了滚动。继续查找资料,发现了下面的标准说明:

用户代理必须将根元素上设置的 overflow 属性应用于 viewport 。当根元素是一个 HTML 的 html 元素且它的 overflow 值是 visible ,并且它拥有 body 元素作为后代,用户代理必须将这样的第一个后代元素的 overflow 的属性值应用于 viewport 。如果 visible 应用于 viewport ,则必须将其解释为 auto 。从中传播值的元素必须有一个 overflow 已使用的值为 visible

从上面的说明以及实际测试,推测上述情况中 body 的 overflow 属性应用到了 viewport 上,导致产生了滚动。把 body 设置 overflow-x: hidden ,就发现不会产生左右滚动了,移动端访问如下:

59-qrcode-normal

后记

在 IOS 和 安卓上测试了 4、5 个浏览器都会出现左右滚动,但一些安卓手机上的 Chrome 浏览器没有出现滚动,例如在红米6手机。还有一点就是:PC 端浏览器都不会产生左右滚动。

此外,标准上其实说的并不是很清楚,再加上标准是一回事,各家实现并不一定都是符合标准。

以上见解都是个人结合资料和实际测试得出,是否真是这样,真不好说,不过可以当作一个思考的方向。

参考资料

查看原文

赞 0 收藏 0 评论 0

XXHolic 发布了文章 · 2020-01-03

兼容性常规检测

引子

碰到检查支持 font-family 的疑问,一时想不出,查了资料后解惑。顺便在此对是否支持的检测方式,进行一些基本的归纳。

HTML 检测

浏览器并不会对 HTML 进行编译,而是直接解析并显示结果,并以宽松模式运行。即使用了错误的语法,浏览器通常都有内建规则来解析书写错误的标记,页面仍然可以显示出来。对于比较新的标签,大都会有对应的 JS 对象可以进行检测。

不支持脚本检测

<noscript> 元素中定义脚本未被执行时的替代内容。

<noscript>Your browser does not support JavaScript!</noscript>

CSS 检测

通过 style 对象

通过获取 style 对象的某个属性值是否为字符串来判断。

typeof document.body.style.width // string
typeof document.body.style.test // undefined

supports 方法

浏览器原生提供 CSS 对象里面有个 supports 方法,用来检测浏览器是否支持给定的 CSS 规则,返回布尔值。
需要注意的是 window.CSS 这个对象并不是所有浏览器都支持。

window.CSS.supports("display: flex") // true
window.CSS.supports("display: test") // false
window.CSS.supports("display", "flex") // true
window.CSS.supports("display", "test") // false

Modernizr 是一个检测是否支持 HTML5 和 CSS3 特性的库。

事件检测

可以创建一个元素对象,然后检查在该对象中,是否有对应的属性。

function isSupportEvent(eventName) {
  if (typeof eventName !== 'string') {
    console.log('Event name is not legal !');
    return;
  }
  var element = document.createElement('div');
  eventName = 'on' + eventName;
  var isSupport = Boolean(eventName in element);
  return isSupport;
}

浏览器模型对象检测

这类都在 window 对象上,直接获取进行判断。对于支持对象,但部分支持对象拥有的方法,类似方式进行判断。

function isSupportObject(objName) {
  if (typeof objName !== 'string') {
    console.log('Object name is not legal !');
    return;
  }
  return Boolean(window[objName]);
}

字体检测

检测字体的思路是:初始化一个字符串,设置通用字体,获取其渲染宽度,然后设置需要检测的字体,获取渲染的宽度,比较两个宽度,相同说明不支持,不同说明支持。类似思路的还有这种方式

function isSupportFontFamily(font) {
  if (typeof font !== 'string') {
    console.log('Font name is not legal !');
    return;
  }

  var width;
  var body = document.body;

  var container = document.createElement('span');
  container.innerHTML = Array(10).join('wi');
  container.style.cssText = [
    'position:absolute',
    'width:auto',
    'font-size:128px',
    'left:-99999px'
  ].join(' !important;');

  var getWidth = function (fontFamily) {
    container.style.fontFamily = fontFamily;
    body.appendChild(container);
    width = container.clientWidth;
    body.removeChild(container);

    return width;
  };

  var monoWidth  = getWidth('monospace');
  var serifWidth = getWidth('serif');
  var sansWidth  = getWidth('sans-serif');

  return monoWidth !== getWidth(font + ',monospace') || sansWidth !== getWidth(font + ',sans-serif') || serifWidth !== getWidth(font + ',serif');
}

参考资料

查看原文

赞 0 收藏 0 评论 0

XXHolic 发布了文章 · 2019-12-27

CORS

引子

CORS 全称 Cross-Origin Resource Sharing,跨源资源共享,是跨域的解决方案之一,里面有不少的知识点,在此集中整理。

简介

浏览器的同源策略是一个重要的安全机制,不同源的客户端在没有授权的情况下,不能够访问对方的资源。同源的定义是访问链接的协议、域名和端口号均相同。在实际应用中,合理的跨域请求对于一些应用程序也很重要, CORS 标准定义了在访问跨域资源时,浏览器与服务器应该如何沟通。CORS 的基本思想是使用 HTTP 头部让浏览器与服务器进行沟通,从而决定请求是否能够成功。

CORS 标准中新增了一组 HTTP 首部字段,用于浏览器和服务器之间沟通。在跨域请求中,在一些情况下会有一个预检请求(preflight request),是用来检查是否允许这种类型的请求,这种请求使用 OPTIONS 方法。预检请求的使用,可以避免跨域请求对服务器的数据产生未预期的影响。接下来看看相关具体的内容。

Request Header

CORS 涉及以下的请求头:

Origin

表示跨域请求或预请求来自哪里。

Access-Control-Request-Method

在使用 OPTION 方法时会用到,表示对同一资源的将来跨域请求可能使用的方法。

Access-Control-Request-Headers

在使用 OPTION 方法时会用到,表示对统一资源将来跨域请求可能使用的请求头部。

Response Header

CORS 涉及以下响应头:

Access-Control-Allow-Origin

表示是否能够共享响应。如果服务器认为请求可以接受,就设置该头部为请求头的 Origin 信息或者 * ;如果没有这个头部,或者这个头部的信息跟请求的 Origin 信息不匹配,浏览器就会驳回请求。

Access-Control-Allow-Credentials

表示跨域请求是否提供凭据。默认情况下,跨域请求不提供凭据,设置该头部为 true 时,表示对应的请求应该发送凭据。如果服务器的响应中没有设置该头部,但发送的请求中带了凭据,浏览器会调用到 onerror 事件处理程序。如果是 fetch 请求,该值设置为 include

Access-Control-Allow-Methods

表示跨域请求支持的方法。

Access-Control-Allow-Headers

表示跨域请求支持的头部。

Access-Control-Max-Age

表示预请求可以缓存多长时间,以秒为单位。

Access-Control-Expose-Headers

通过列出其名称,指示哪些头部可以作为响应的一部分公开。

Preflight Request

上面有提到在一定条件下,会先触发预检请求,当请求满足下面任一条件时,就需要先发预检请求:

  • 使用 PUTDELETECONNECTOPTIONSTRACEPATCH 中任一方法。
  • 人为设置了对 CORS 安全的首部字段集合之外的首部字段,该集合在正式标准中包含 AcceptAccept-LanguageContent-LanguageContent-Type(还有额外限制)。
  • Content-Type 的值不是 application/x-www-form-urlencodedmultipart/form-datatext/plain 其中之一。
  • 请求中 XMLHttpRequestUpload 对象注册了事件监听器。
  • 请求中使用了 ReadableStream 对象。

如果请求满足下面所有条件,就不会触发预请求:

  • 使用 GETHEADPOST 方法之一。
  • 不得人为设置对 CORS 安全的首部字段集合之外的首部字段,该集合在正式标准中包含 AcceptAccept-LanguageContent-LanguageContent-Type(还有额外限制)。
  • Content-Type 的值仅限 application/x-www-form-urlencodedmultipart/form-datatext/plain 其中之一。
  • 请求中 XMLHttpRequestUpload 对象没有注册任何事件监听器。
  • 请求中没有使用了 ReadableStream 对象。

参考资料

查看原文

赞 0 收藏 0 评论 0

XXHolic 发布了文章 · 2019-12-21

Webpack 一些概念

引子

打包工具有多种,实际中 webpack 接触的比较多,目前中文文档跟英文文档有些对不上,其中有些概念比较分散,对此进行集中的整理。

Dependency Graph

任何时候,一个文件依赖于另一个文件,webpack 把这种情况视为依赖关系。这让 webpack 可以接受非代码资源,例如图片或字体,并且可以将它们作为依赖提供给你的应用程序。

当 webpack 处理你的程序时,可能是从命令行或配置文件中定义的一系列模块开始。从入口文件开始,webpack 递归地构建一个依赖图(Dependency Graph),这个依赖图包含着应用程序所需的每个模块,并生成一个或多个 bundle ,可由浏览器加载。

Runtime

  • runtimemanifest 数据,基本上是在浏览器运行时,webpack 用来连接模块化应用程序所需的所有代码。
  • 它包含在模块交互时连接它们所需的加载和解析逻辑。这包括连接已经加载到浏览器中的模块,以及延迟加载尚未加载的模块的逻辑。

Manifest

  • manifest 作为名词时,中文意思是:(船或飞机的)货单,旅客名单
  • 当编译器进入、解析和映射你的应用程序时,它会在你的所有模块上保留详细的信息,这个数据集合称为“manifest”。
  • 当完成打包并发送到浏览器时,runtime 会通过 manifest 来解析和加载模块。
  • 无论你选择哪种模块语法,那些 importrequire 语句现在都已经转换为 __webpack_require__ 方法,此方法指向模块标识符(module identifier)。

Module、Bundle、Chunk

Module

  • 比完整程序更小的接触面的分离的块(chunk)功能,使得验证、调试、测试轻而易举。
  • 精心编写的模块提供了可靠的抽象和封装边界,构成了连贯的设计和清晰易懂的目的。

Bundle

  • 作为名词时,意思是:(一)捆,包,扎。
  • 从多个不同的模块产生,包含已经加载和编译过程的源文件的最终版本。
  • 个人理解就是一些相关联的包打包成的一个文件。

Chunk

  • 作为名词时,意思是:块,组块,话语组成部分。
  • 这个 webpack 中特定术语在内部用于管理打包过程。
  • bundleschunks 组成,其中有多种类型(例如入口)。
  • 通常,chunks 与输出 bundles 直接对应,但是,有些配置不产生一对一关系。

Bundle Splitting

  • 这个过程提供了一种优化构建的方法,允许 webpack 为单个应用生成多个 bundle
  • 它可以将每个包的更改隔离开来,而不影响其它包。
  • 这样利用浏览器的缓存,可以减少需要重新发布和客户端下载的代码量。

Code Splitting

指将代码分成不同的 bundles/chunks ,然后可以按需加载,而不是加载包含所有内容的单个 bundles

Tree Shaking

  • 消除多余和未使用的代码。
  • webpack 通过分析各种导入语句和导入代码的使用,以确定实际使用的依赖项,删除不属于“tree”的一部分。

Output Filename

webpack 提供了一种使用称为 substitution (可替换模板字符串) 的方式,通过带括号字符串来模板化文件名。

模版描述
[hash]模块标识符的 hash。修改一个模块,其它模块生成的 hash 都会变。
[contenthash]文件内容的 hash,每个都不相同。只有改变了内容的文件的 hash 才会变。
[chunkhash]chunk 内容的 hash。一个文件改变,其关联的文件 hash 也会变。
[name]模块的名称
[id]模块标识符
[query]模块的 query,例如文件名 ? 后面的字符串
[function]返回文件名称的方法

参考资料

查看原文

赞 1 收藏 0 评论 0

XXHolic 发布了文章 · 2019-12-12

富文本编辑

引子

再次碰到需要使用富文本编辑的场景,发现了之前没有想到的一些点,在此整理一下。

参考点

在使用富文本插件的时候,在比较选择时,个人发现以下几个参考点:

  • 文档说明,这个可以节约很多时间,无论是比较和使用时。
  • 国际化,有的真没有国际化,连文档也没有说明。
  • 实现列表、链接、标题等各种效果是否使用了原生的 HTML 标签,其样式跟自身系统样式重置是否有冲突,其它显示编辑后富文本的地方也要考虑。
  • 字体的支持,加载额外的字体文件可能加大包的体积。
  • 图片上传的处理,有的插件需要自己写。
  • 视频插入的处理,有的插件只是插入一个链接,不同的视频源可能效果会不一样。
  • 判空,富文本里面可能一开始有默认的标签,只是看不到,获取的时候也拿得到,但实际上是没有输入内容。
  • 初始化、内容改变、获取/失去焦点事件监听,嵌入其它框架时有用处。
  • 扩展,这个分为自定义和覆盖原有功能两种形式,看实际需求。

关于字体方面,见 Font

下面就是找到的一些免费开源的富文本插件比较。

插件

以下仅供参考。

Quill

文档上说,Quill 是一个为兼容性和可扩展性而构建的所见即所得的现代编辑器。

截至 2019.12.12, 仍然处于有人维护状态,结合上面的考虑点,有下面的一些个人体会。

好的方面:

  • 有独立的包,大部分功能都不需要额外安装其它依赖。
  • 实现的格式效果,有自身单独的处理,例如 ul 标签的样式。
  • 支持部分视频网站分享链接插入。
  • 扩展支持自定义和覆盖。
  • 展示风格简约。

不好的方面:

  • 文档说明感觉不怎么好用。
  • 没有国际化。
  • 图片异步处理需要单独重写处理,没有提供相应的 API 。
  • 工具栏和操作没有对应的提示信息。

Summernote

文档上说,Summernote 是一个超级简单的所见即所得的编辑器。

截至 2019.12.12, 仍然处于有人维护状态,结合上面的考虑点,有下面的一些个人体会。

好的方面:

  • 文档比较完整好找。
  • 事件和方法丰富,包含图片上传异步处理 API。
  • 支持国际化。
  • 工具栏和操作都会有提示信息,而且图片、表格伴有浮动操作工具。
  • 支持部分视频网站分享链接插入。
  • 扩展支持自定义。
  • 展示风格简约。

不好的方面:

  • 需要安装额外的依赖包 JQuery。
  • 依赖 Bootstrap 的样式和部分组件,但不是必需,也有不依赖的版本,如果觉得不好看,需要自行写额外样式调整。
  • 编辑时的效果使用的是 HTML 标签原生的效果,例如 H 系列、ul、ol、table 。展示的地方需要重置样式。

UEditor

文档上说,UEditor 是所见即所得富文本 web 编辑器,具有轻量,可定制,注重用户体验等特点。

已无人维护了,在实际中使用过,主要功能还是没有问题的,结合上面的考虑点,有下面的一些个人体会。

好的方面:

  • 文档详细,因为开发人员也都是国人。
  • 独立的包。
  • 支持国际化。
  • 实现的格式效果,部分有自身单独的重置处理。
  • 功能很强大且丰富,一般富文本有的都有。
  • 草稿本地存储。
  • 扩展支持自定义。

不好的方面:

  • 使用时初看像早期编辑器,一些交互的弹窗也很原始。
  • 使用的功能越多,配置相对其它插件要复杂一些。

Slate

文档上说,Slate 是一个完全可定制的富文本编辑框架。

截至 2019.12.12, 仍然处于有人维护状态,结合上面的考虑点,有下面的一些个人发现。

好的方面:

  • 文档列出了内部实现的相关属性和方法,有中文版。
  • 独立的包。
  • 实现的格式效果,有自身单独的重置处理。
  • 支持部分视频网站分享链接插入。
  • 选择内容后支持浮动的操作工具。
  • 有对应定制的 react 版本。
  • 扩展支持自定义。

不好的方面:

  • 无国际化。
  • 偏重定制化,想要添加比较多的功能,需要花时间找对应包,看对应包文档。
  • 工具栏的交互跟其它插件有些不一样,要先选择内容再选择格式才有效。
  • 提供的图片上传示例只支持图片源 URL 的方式。
  • react 使用起来,看文档比较复杂。

参考资料

查看原文

赞 3 收藏 2 评论 0

XXHolic 发布了文章 · 2019-12-09

textarea 换行处理

引子

textarea 中的换行格式,在其它地方显示时,需要保持其原有的换行格式。

换行

textarea 元素支持多行纯文本编辑。由于历史原因,元素的值有三种不同的形式:

  • row value 是其原始设置的值,它没有被标准化。
  • API value 是 value 接口定义中使用的值。它是标准化的,因此换行符使用 “LF”(U+000A) 字符。
  • 表单提交的 value。它是标准化的,因此换行符使用 U+000D CARRIAGE RETURN "CRLF" (U+000A) 字符对。

关于换行符,见回车和换行

要按照输入时格式显示,方法有:

使用 pre 标签

<pre> 元素表示预定义格式文本。在该元素中的文本通常按照原样的编排,以等宽字体的形式展现出来,文本中的空白符(比如空格和换行符)都会显示出来。

替换换行符

将换行符替换成 br 标签,以 innerHTML 的方式显示在元素中。

  // windows 下换行是 \r\n, linux 下是 \n, mac 下是 \r
  str = str.replace(/[\r\n]/g, '<br />');

这是以上方法的示例,移动端访问如下:

57-qrcode

参考资料

查看原文

赞 0 收藏 0 评论 0

XXHolic 关注了用户 · 2018-03-30

Pines_Cheng @pines_cheng

不挑食的程序员,关注前端四化建设。

关注 284

XXHolic 关注了标签 · 2018-03-30

程序员

一种近几十年来出现的新物种,是工业革命的产物。英文(Programmer Monkey)是一种非常特殊的、可以从事程序开发、维护的动物。一般分为程序设计猿和程序编码猿,但两者的界限并不非常清楚,都可以进行开发、维护工作,特别是在中国,而且最重要的一点,二者都是一种非常悲剧的存在。

国外的程序员节

国外的程序员节,(英语:Programmer Day,俄语:День программи́ста)是一个俄罗斯官方节日,日期是每年的第 256(0x100) 天,也就是平年的 9 月 13 日和闰年的 9 月 12 日,选择 256 是因为它是 2 的 8 次方,比 365 少的 2 的最大幂。

1024程序员节,中国程序员节

1024是2的十次方,二进制计数的基本计量单位之一。程序员(英文Programmer)是从事程序开发、维护的专业人员。程序员就像是一个个1024,以最低调、踏实、核心的功能模块搭建起这个科技世界。1GB=1024M,而1GB与1级谐音,也有一级棒的意思。

从2012年,SegmentFault 创办开始我们就从网络上引导社区的开发者,发展成中国程序员的节日 :) 计划以后每年10月24日定义为程序员节。以一个节日的形式,向通过Coding 改变世界,也以实际行动在浮躁的世界里,固执地坚持自己对于知识、技术和创新追求的程序员们表示致敬。并于之后的最为临近的周末为程序员们举行了一个盛大的狂欢派对。

2015的10月24日,我们SegmentFault 也在5个城市同时举办黑客马拉松这个特殊的形式,聚集开发者开一个编程大爬梯。

特别推荐:

【SF 黑客马拉松】:http://segmentfault.com/hacka...
【1024程序员闯关秀】小游戏,欢迎来挑战 http://segmentfault.com/game/

  • SF 开发者交流群:206236214
  • 黑客马拉松交流群:280915731
  • 开源硬件交流群:372308136
  • Android 开发者交流群:207895295
  • iOS 开发者交流群:372279630
  • 前端开发者群:174851511

欢迎开发者加入~

交流群信息


程序员相关问题集锦:

  1. 《程序员如何选择自己的第二语言》
  2. 《如何成为一名专业的程序员?》
  3. 《如何用各种编程语言书写hello world》
  4. 《程序员们最常说的谎话是什么?》
  5. 《怎么加入一个开源项目?》
  6. 《是要精于单挑,还是要善于合作?》
  7. 《来秀一下你屎一般的代码...》
  8. 《如何区分 IT 青年的“普通/文艺/二逼”属性?》
  9. 程序员必读书籍有哪些?
  10. 你经常访问的技术社区或者技术博客(IT类)有哪些?
  11. 如何一行代码弄崩你的程序?我先来一发
  12. 编程基础指的是什么?
  13. 后端零起步:学哪一种比较好?
  14. 大家都用什么键盘写代码的?

爱因斯坦

程序猿崛起

关注 143396

XXHolic 关注了标签 · 2018-03-30

安全

网络系统的硬件、软件及其中数据受到保护,不受偶然的或者恶意的破坏、更改、泄露,保证系统连续可靠地运行,网络服务不中断的措施。

关注 50985