浏览器切换中

最近比较忙,博客有些荒废。忙过这阵子再写吧。

接下来的三周,会强迫自己把开发浏览器切到 Firefox,争取从现有项目中找到兼容性问题,并且修复。

再接下来如是反复,Chrome > Firefox > Edge > Safari > Chrome……

以后客户越来越多了,兼容性得多费心了。

下午4点更新

Firefox 不支持 SourceMap……已经切回 Chrome 了,晚上继续尝试。

8-14 更新

Firefox 团队曾经抱怨 Google Chrome 不正当竞争,故意在几个大型产品中给 Firefox 挖坑,打压他们在用户心目中的地位。然而经过我这一周来的使用,我觉得 Firefox 实在做得不够好,很难让开发者选择它作为主力开发工具。

  1. 使用 Vue Cli 会不停报错,找不到 sw.js,连接不到 sockjs-node 等等
  2. 使用 webpack 无法加载到 sourcemap,无法 debug。(应该是配置问题,因为 Vue Cli 3 可以。
  3. 报错多了就影响性能,各种卡顿。
  4. 断点不会自动跳过去,屏幕没有明显的视觉效果,开始被迫重启好几次
  5. 断点不会自动更新堆栈、变量状态,需要点击一次“Step over”

问题很多,很影响开发效率。可惜我厂老板特别爱用 Firefox,所以我还必须照顾下 Firefox,经过一周的挣扎,现在倒是好一些了,Windows 10 的表现还不错。

使用 `position: sticky` 的要点

position 属性非常重要,它有五个可选值。“这五个选项是哪些?它们的作用如何?”是我非常喜欢的面试题。以我的经验,凡是这道题答得好的,后面多半没啥问题;这道题答不出或者错漏的,后面能翻盘的概率很低。

属性及释义大家请看 MDN,本文不再赘述。

position: sticky 比较复杂,简单来说,它包含以下特性:

  1. 当它的位置让它可以正常呈现的时候,它的定位等同于 position: static,随着正常的文档流滚动
  2. 当它的位置不足以让它正常显示,但它的父元素有足够多让它显示的空间,它的定位等同于 position: fixed
  3. 当它的父元素的空间不够让它显示,它的定位等同于 position:absolute

言说太复杂,大家可以看下这个演示,基本上就能明白了:

继续阅读“使用 `position: sticky` 的要点”

使用 <wbr> 解决长 URL 的换行问题

问题

我们知道,世界上文字主要有两种:一种是以中文为代表的象形文字;另一种是以英法俄等为代表的拼音语系。前者的换行很简单,每个单字都有自己的意义,所以每个字后面都可以换行。拼音语言,字母组合本身无意义,连在一起才有意义;不同单词意义差异巨大,所以只能以单词为单位换行。

Web 开发中,屏幕宽度有限,超长文字必须换行。在 CSS 中,控制换行的属性主要有 word-breakwhite-space,其中,默认换行行为的是 word-break: normal,即以单词为单位换行。比较奇怪的是,对于 URL,我本以为类似 /.: 都是明显的单词分隔符,理应换行,但实际上,浏览器并不会在这些地方换行。如果我们使用 break-all 或者 break-word,则会使得浏览器在不合理的地方换行,如果刚好在表格里,别的列内容比较多,那么包含 URL 的单元格就会被挤压得非常窄,拉得特别高,非常难看,非常难读。

尝试

原生方法无法解决问题,只好摸索手动断行的做法。但是想完美解决问题非常困难:

第一个方案是全部换行,肯定不行;

第二个方案固定宽度换行,因为表格内容不固定,效果也很差,也不行;

老板提出了第三个方案:使用“8.3”格式,即超长字符串只保留前8个字符,后面显示“…”,然后可以手动展开。很明显,这个方案对 URL 来说没有什么价值,https:// 加起来正好 8 个字符,有意义么……即使加长也一样,因为用户有时候看域名,有时候看 pathname,也有时候看 search,我们没有办法预测。

然后老板又提出“Excel 方案”,即固定列宽,自动隐藏超出的文字,用户可以通过拖拽来调整列宽。这个方案理论上可以解决问题,但是实现难度太大,因为浏览器自带表格自适应宽度的算法,采用 “Excel 方案” 就必须放弃这个算法自己手动实现,成本很高,非万不得已也不想做。

最后,动态换行,根据表格宽度计算在哪里断行。还是不行,计算难度太大。

<wbr> 解决

这个问题困扰了我很久,直到前两天,我突然发现原来有 <wbr> 软换行的存在。而且它的兼容性非常之好,甚至连 IE8 都支持。

它的含义是“可换可不换”。当元素宽度不够需要换行,就从它这里换;如果宽度够,就不换行。所以,只需要在“可能”换行的地方加上这个元素,就可以达成我的目标。写成代码很简单,大约是这样:

function wrapUrl(url) {
  if (!url) {
    return '';
  }

  // 先把协议取出来,我不希望在协议这里换行
  const head = url.substring(0, 10);
  const left = url.substring(10);
  // 在 `?&/` 前面插入 `<wbr>`
  // 或者16个连续英文数字也要换行,打断 hash 和 md5
  return head + left.replace(/([?&\/]|([a-zA-Z0-9]{16}))/g, str => '<wbr>' + str );
}

实际效果很好,大概是这样(截图时,<wbr> 放在断开位置的后面,我觉得不好看,就调整了下):

<br> 对比,后者是固定换行,当表格内容很少,有充足的空间显示 URL 时,也会换行,就不合适了。

总结

需要注意,<table> 的渲染很特殊,浏览器要花很多时间计算每个列的内容、计算它的宽度,所以性能会比较差,这也是不要用 <table> 做布局的原因。本案例中,使用 <wbr> 实际上是想借用浏览器计算表格各列宽度的机制。所以是合适的。表格渲染之后,内容最好就固定住,不要有复杂的变动,比如隐藏/显示(前面说的8.3格式),因为内容的变化会导致浏览器重新计算布局重新渲染,比较消耗机器的性能。

以及,做了十几年前端,稍一放松,竟然有完全不清楚没用过的标签,看来有必要找时间再把 HTML、CSS 再翻一遍了。

Stylus 实现 `content: “5”`

一般来说 Stylus 对属性是直接替换的,所以正常来说下面的 stylus

$a = 10

.foo::before
  content $a

会编译成:

.foo::before {
  content: 10;
}

这样是非法的,10 不会渲染出来。如果想让它渲染出来,必须用字符串。如果只有一个值,那就好办了,直接 $a = '10' 就好。但如果在循环里,就比较麻烦,此时,可以用 '' + 10 转换成字符串,或者使用 s(template, value),以下两种方式是等效的。

// 注意运算顺序哦
.foo
  for n in 1..5
    &:nth-child({n})::before
      $str = '' + (5 - n)
      content $str

  for n in 1..5
    &:nth-child({n})::after
      $str = 5 - n
      content s('"%s"', $str)

近期用 webpack-dev-server 作代理的一些经验

使用 webpack-dev-server 代理远程服务器;修改 http header 以便本地登录;代理 WebSocket。

如今前端开发使用 webpack-dev-server 作为本地服务器已经是基本配置,加上 proxy 功能可以很好的应对 SSL、跨域、线上环境切换等需求。Vue CLI 3 里也做了相应的集成,用起来很方便。

继续阅读“近期用 webpack-dev-server 作代理的一些经验”

在 Pug 模板中使用变量

如何在 Pug 里,把变量注入父模板。

我厂使用 Pug 作为 HTML 的预编译工具,写久了发现回不去了……Pug 写起来很高效,看久了习惯了阅读效率也很高,读了读文档,发现还有很丰富的可编程特性。于是我决定抛弃 Handlebars,以后都用 Pug 写模板了。

这次我厂官网改版,我就用 Pug 替换了 Handlebars,于是 build 脚本一下少了几十行,非常爽。其实我早就想这样做,不过卡在一个点:使用变量。

在 Pug 里可以这样使用变量:

- var name = 'meathill'

h1 hello #{ name }

以上代码将输出 <h1>hello meathill</h1>。这种用法比较简单,不过在 extends模板的时候,把变量放到哪里就不知道了,官方文档也语焉不详,我反复试了很多次无果,最终还是靠搜索找到了答案,原来要这样:

// Layout.pug
html
  block vars
    p 注意,这个 block 是重点,它出现在前面,用来注入变量
  head
    title #{ title } | My site
  body
    block content

// page.pug
extends Layout

block vars
  - var title = 'A blog'
  
block content
  h1 Blog title
  p blog content

关于“大漠”的一点不好的记忆

关于大漠的一点不好的记忆。不过当年没搞清楚到底是哪个。发生了最近的事情,我觉得可能是这个……

我这个人不爱交际,在行业里一直是个小透明。

不过我当年也曾想试着改变一下自己,所以找了个前端交流群加了进去。具体的群名字忘记了,总之是“大漠”创建的群。

然后发现这个群真TM恶心。群主大漠有几个小弟,一副高高在上颐指气使的样子,对群里成员吆五喝六随意攻击,不仅对发问者提出各种限制,对回答者也提出各种限制。群主也是一副老子对你们这些废柴施恩,还不快快对我歌功颂德的气势。(具体做法记不清了,好多年了。)

然后我就受不了啦。如果前端圈子都这个吊样子,那我不交际也不要紧。于是吐槽两句自己退群了。

退了之后,想说这个大漠是谁啊,怎么这么差劲,然后搜了一下,发现叫这个名字的好多,无法确定是哪个,我也懒得跑回去确认。


发生了最近的事情后,我举得按人品和气度,有可能是这个……

准备搞一场专题直播

准备在 SegmentFault 搞一场专题直播,标题是《jQuery, Backbone, Vue》,计划通过对比老中青三代框架开发的差异,带领大家理解前端发展的趋势,接触更好的未来。https://segmentfault.com/l/1500000008694676 求关注,求购买。

准备在 SegmentFault 搞一场专题直播,标题是《jQuery, Backbone, Vue》,计划通过对比老中青三代框架开发的差异,带领大家理解前端发展的趋势,接触更好的未来。

直播地址:https://segmentfault.com/l/1500000008694676
幻灯片地址:https://meathill-lecture.github.io/jquery-backbone-vue/
范例代码仓库:https://github.com/meathill-lecture/jquery-backbone-vue

求关注,求购买。

一个超级诡异的 iOS Safari `position: fixed` 失效问题

iOS Safari 下,`input readonly` 仍然可以获得输入焦点,此时页面交互大部分正常,但 `position:fixed` 会受影响。

今天前同事李某找我咨询 Hybrid 开发的问题,想起来大前天搞这个问题搞了一天,赶紧记下来,省得忘记。

先说需求。东家让我做个日历组件,在手机 Web 上用。组件的样式是这样的,很多地方都可以见到,比如南航国航的客户端。

日历控件需求图

看起来并不复杂,事实上也是,基本上顺顺利利的开发完成,准备交付。这里有个伏笔,开发中我按老习惯,使用桌面 Chrome,和实际生产环境不太一样。不过我自然要去真机上测试,结果一测问题就出来了。

因为组件需要全屏展示,所以我设置了如下的CSS:

.date-picker {
  position:fixed;
  top:0;
  left:0;
  right:0;
  bottom:0;
  background-color: white;
  z-index:1024;
}

同时,对原本的 <input name="date">,我给它加上 readonly,避免弹出虚拟键盘。理论上,这样的就可以了。但实测时,不滚屏的时候,组件弹出时尺寸是准确的,盖满全屏;然则一旦滚屏,组件就会占据从页面最上方到当前最下面这截位置。大约相当于 position:absolte;top:0 的效果。

Safari 截图
手机截图
如图,可以看到组件占据了全屏,但实际是从页面最上面开始的,定位有问题。用桌面 Safari 调试也可以看出来它的高度是 968,远大于正常的 667。

这很诡异,上下左右全为0,是上古巨兽 IE6 都支持的做法。iOS Safari 虽然 Bug 多多,不应该连这个都有毛病啊。以 ios safari position fixed 为关键词 Google 之,结果 iOS Safari 历史不清白,当年 iPhone 刚出的时候的确有定位问题,于是虽有满屏的结果,但都不适用。

然后我想到找其它库,比如 Bootstrap,它的 Modal 组件也是类似的效果。但是怎么测都正常,于是我只好一个样式一个样式修改,仍然没有结果。

时间慢慢流逝,转眼已经凌晨2点了,就在我几欲放弃之际,突然发现,虽然组件弹出的时候定位有问题,但只要我点掉下面的完成,定位就会立刻恢复正常。

手机截图
注意,就是那个“完成”。

问题至此已经明朗:在 iOS Safari 里,即使 <input> 设置了 readonly,它仍然可以获取输入焦点。获取输入焦点之后,虽然没有弹出虚拟键盘,但仍然是待输入状态。

此时页面各种交互都是正常工作的,比如点击、滚屏。唯独 position:fixed 定位有问题。点击“完成”离开输入状态,Safari 自动刷新页面元素,定位就正常了。

于是我在组件弹出后,自动 input.blur(),使其失去焦点,组件的尺寸便正常无误了。


总结

移动端 Web 开发总有各种各样稀奇古怪的问题。有些好解决,有些不好解决,比如这个问题,很难定位:

  1. 历史不清白,搜也搜不到
  2. 组件要求全屏,需要避免虚拟键盘,所以会改变默认行为
  3. 其它情况下都是好的

我能想到的方案,就是想办法,用所有能用的工具,排除掉所有其它问题,最终还是能搞出来的。

FontAwesome 通过组合创建新图标

复合使用 FontAwesome 图标,创建符合需求的新图标。比如把“图片(fa-picture-o)”和“加(fa-plus-circle)”叠到一起,就可以创造”新增图片“的图标。

FontAwesome 提供了很多好看的图标,使用 WebFont 嵌入页面更是简单又好用,所以我基本上一直用它。不过有时候还是觉得不太够用,这就需要复合使用多个图标。

下面是个例子,我在图标的右下角增加一个圆形加号,表示增加、创建。

See the Pen add icon by Meathill (@meathill) on CodePen.

这里有两个选择:

  1. FontAwesome 提供了一个堆叠图标的样式:fa-stack,可以堆叠任意多的图标。
  2. 因为它实际上只占用了 :before,所以也可以使用 :after 来容纳新增的图标。如果只需要两个图标,这个方式更简单。