Blink 系瀏覽器 History API scroll restoration 的 bug

March 03, 2016

Update 2016/04/02 15:30 Opera 36(.0.2130.46) 跟 Chrome 49 同步了,所以這個問題也消失了。此文章正式壽終正寢(?)

Update 2016/03/06 14:00 更新到 Chrome 49(.0.2623.75) 以後這個問題已經修正(bug & commit);而同樣是 Blink 系的 Opera 更新比較慢,在 Opera 35(.0.2066.92) 上還是可以重現問題。


最近寫的東西幾個主要的 view 都跟捲動位置有關,而捲動位置對網站瀏覽者的體驗事關重大。

本文內容針對桌面瀏覽器。

我覺得良好的捲動位置體驗是

  1. 在同頁面重新整理後會回到一樣的位置 (在內容物不變的前提之下)
  2. 承 (1),如果內容物在重新整理後會改變 (ex: Facebook),重新整理後可以乾脆回到頂端
  3. 使用瀏覽器的「上一頁」、「下一頁」進入頁面時會保持之前瀏覽的位置
  4. 曾經瀏覽過的頁面再次透過連結點擊進去後可以重置捲動位置(不需要有 3. 的行為)

上面這幾點基本上完全在現在瀏覽器的掌握之中,然而最近發現的 Blink 系瀏覽器(Chrome、Opera)在使用 History API 時的 scroll restoration 有點奇怪

  • 程式操作後的 scroll 並不會被記錄
  • 預設的捲軸 restoration 不適合一般 single page app

這兩點發生的一個重要前提 - 「在操作之後,使用者沒有再動過捲軸(拉動 scrollbar 或是用滾輪)」

Read on 

webpack chunk 讀出來是空的該怎麼辦

February 12, 2016

總之最近有需求所以用上了 webpack 的 code splitting,就在用爽爽以為世界為我所有的時候,碰到神秘的現象。

環境

為了稍微釐清一下關係,以下的主角分別稱為 A & B

A - 嵌入式的工具,設計為可以嵌入各種網站使用(使用 webpack code splitting) B - 新的 project(使用 webpack),有嵌入 A 做進階使用

這天 B 需要嘗試加入 code splitting,目前只需要分成兩個 chunk,一個 chunk 代表一個 route。

簡單的 code splitting 示意寫法如下:

import dyna from '_util/createDynamicComponent';

const Main = dyna((resolve) => {
  require.ensure([], require => {
    resolve(require('_components/Main').default);
  });
});

照常理來說,webpack 會確保 require.ensure 的 callback 裡面需要執行的東西在讀進來之後才執行;然而,此處的 require('_components/Main') 在實際執行的時候,卻時不時是空的 {}

設身處地的想想,這個 bug 有多難 deeeeeeeeeeeeeeee

webpack magic

在經過多方交叉測試確認不是我程式碼這邊的問題之後,所有的矛頭指向了 webpack。但 webpack 的能力太強、文件寫得不夠詳盡,對我來說 code splitting 這件事就是一個 magic,也因此還真的不知道該怎麼 de webpack 的 bug。

於是我在 google 上搜尋各種可能的 keyword

webpack chunk load failed webpack chunk doesn’t load

然後實際上很莫名的會一直搜到它的文件,以及各種 tutorial 或 opinion 文章,頓時真是覺得這個生態系是不是有什麼問題…

直到我從上面的環境中 identify 了關鍵的可能原因,重新用了這個 keyword

webpack multiple instance

搜尋結果其實看起來還是一樣沒有一矢中的的感覺,不過答案其實默默地藏在第三個結果裡。

Ref: https://github.com/webpack/docs/wiki/configuration#outputjsonpfunction

output.jsonpFunction The JSONP function used by webpack for asnyc loading of chunks. A shorter function may reduce the filesize a bit. Use different identifier, when having multiple webpack instances on a single page.

output.jsonpFunction

有稍微 trace 一下的話,可以知道 webpack 想要載入 chunk 的時候會需要使用這個一個名為 webpackJsonp 的 global function (在 window 底下),而這個設定 output.jsonpFunction 就是用來設定這個 function name 的。

在同一個網頁上面有多個 webpack instance 的時候 (ex: AB 都是 webpack,而且都有做 code split),這個 jsonpFunction 的名字相同會讓兩者其中一方的 chunk loading 陷入錯亂(似乎是編號-based,而編號會混在一起)。

所以最後只需要把 Aoutput.jsonpFunction 改成比較 unique 的就解決了。

TL;DR

條件

  • 同一個頁面會用到兩個以上的 webpack instance
    (一般來說就是兩個從 webpack 面來看沒有相關的 bundle)
  • 有需要做 chunk 載入

需要

  • 透過 output.jsonpFunction 確保兩個 instance 的 jsonp function name 不同
/* file: webpack.config.js */

var config = {
  ...
  output: {
    ...
    jsonpFunction: 'somethingPrettyUniqueJsonp'  // so important
  }
};

後記

是說 multiple webpack instance 需要改 jsonp function name 這種事情這麼重要,也太難找!!!!!!

尋找 YouTube 進度條上的流星

January 23, 2016

開始讀正文之前先來聽首歌吧!

記得當年 YouTube 進度條出來的時候好像造成一片轟動,突然之前大家爭先恐後 implement 它,蔚為風潮;而當年 implementation 中的佼佼者就是 NProgress,到了現在還是盤據一方(?)。

其實之前也不乏做過類似的進度條效果,不過從來沒有特別對 peg 的部分雕琢(或著說根本沒有加)。

什麼是 peg 呢? 來看一下 NProgress 裡面的一段 CSS code

/* Fancy blur effect */
#nprogress .peg {
  display: block;
  position: absolute;
  right: 0px;
  width: 100px;
  height: 100%;
  box-shadow: 0 0 10px #29d, 0 0 5px #29d;
  opacity: 1.0;

  -webkit-transform: rotate(3deg) translate(0px, -4px);
      -ms-transform: rotate(3deg) translate(0px, -4px);
          transform: rotate(3deg) translate(0px, -4px);
}

實際上看起來的效果是

nprogress_peg.png

這個小小的點綴其實為這個進度條增色了非常多,因為進度條本體的高度並不高,透過這個 peg 可以再它動作的時候達到比較好的吸引注意力的效果,讓使用者更容易意識到有什麼東西在動作中。

這個效果其實 YouTube 版本的進度條上面就有,不過我從來沒有好好正眼看過它(一閃即逝嘛)。最近的專案又做了類似 YouTube 的進度條,於是默默決定應該要好好地看一看 YouTube 的 peg 是怎麼做的,便開始了這次尋找流星的旅程。

Read on 

週末長知識: 不換行空格

January 23, 2016

最近處裡一些 HTML 裡面的字串空白,發現在出現   的時候好像用 ' ' 會消不完全… 沒道理啊?

舉個例子

有沒有感覺到一點點的微妙,為何中間的空白不會被 split() 切開呢? 難道從 $(...).text() 取得的中間有些空白不是一般的空白?

接著把上面實驗中的字串拿來研究一下。

// 來源: 上面範例中的 $.trim($('#content').text())
var str = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit.';

console.log(str.charCodeAt(str.indexOf('sit') - 1)));
// output: 160

知道 charcode 是 160 之後應該就很好搜尋囉,於是用 「charcode space 160」 去 google 了一下找到這篇文章:

White-space and character 160

So I used charCodeAt and found it was ASCII character 160. I looked up char code 160 and saw that it is a “Non-breaking space”. You would think that a “space” character would be trimmed. I looked at the jQuery code that does the trimming and the grep pattern uses \s. So, evidently you can’t use \s to catch the “Non-breaking space” in IE. I wonder why no one else has seen this.

乍看好像是古早年代的 IE 問題? 那我怎麼現在會在 modern browser 裡面碰到呢?

接著來試著搜尋中文 「char 160 空白」,發現相關的問題在各種地方出現…

char_160_whitespace.png

突然就看到了 Wikipedia 的連結

眼尖的話會注意到原來在 HTML 裡面的 &nbps; 竟然是也是不換行空格的實現!!!!! 小時候填鴨式學的 HTML,只知道   是一種輸入空白的方法,原來它還有這層特別的含意… 更何況原來它根本不是空白!(說是也可以是啦)

原來 2009 年的 IE 是對的。(根據上面 2009 年的文章,當時的其他瀏覽器都把   輸出為一般的空白 ' ',但 IE 裡面用 regular expression /\s/ 還是 match 不到 '\u00a0')

總之現在 modern browser 們也都是把   轉為這個 '\u00a0',以後碰到類似的東西就不需要大驚小怪囉! 心裡有個底的話,debug 起來會快很多的。

最後補充一下,上面 2009 那篇文章的內容已經與現在瀏覽器的情況不符了。 文中附的 test 頁面 ,展示的 jQuery trim(text) 結果現在看起來是正確的,因為現在的瀏覽器已經確實把 '\u00a0' 視為空白的一種囉!

用 code 來說就是

'\u00a0\u00a0'.replace(/\s/g, '') === '';  // true