歲末年終,一起來幫你家 D3 瘦身吧!

December 20, 2019

最近某專案弄完上線之後,因為想要加快 First Meaningful Paint 的速度,開始調查有沒有甚麼可以改進的地方。

看著看著就看到博大精深(超大包)D3

image-20191219235902640

其實也不過就是 gzip 後 74.7KB 嘛…

本文的終極目標就是希望針對自己的需求,能夠幫 D3 瘦身一下。

不想再聽到瀏覽器的悲鳴了!

D3 為何大包

是這樣的,其實 D3 是一個包羅萬象的超強 library,不能單純的把它定位成「一個畫圖的 library」。

官方介紹如下:

D3.js is a JavaScript library for manipulating documents based on data. D3 helps you bring data to life using HTML, SVG, and CSS. D3’s emphasis on web standards gives you the full capabilities of modern browsers without tying yourself to a proprietary framework, combining powerful visualization components and a data-driven approach to DOM manipulation.

如果你也實際用過 D3,就會瞭解 D3 的強大之處在於它對 資料數據 -> 圖像數字 的處裡非常有一套,我這邊也不說太多,如果想深入一點瞭解 D3,可以參考幾乎每年都有人寫的鐵人賽系列文章:

我是不太推薦完整閱讀「Reac / Vue + D3」的這類型的系列。以想學 D3 的角度來看,介紹 React / Vue 的部分感覺沒什麼卵用,想要讀的話,可以只看其中 D3 的部分就好囉!

而 D3 本身其實被切分為非常多的 package,依據使用情況的不同,會用上不同 package 的功能,一樣推薦一個非常優質的 D3 教學網頁:

Intro to D3

intro to d3

好的,簡單來說就是如果你照官網寫的起手式

<script src="https://d3js.org/d3.v5.min.js"></script>

就會把上面 gif 裡面所有的 package 全部載入網頁裡,這樣做會影響我們 FMP 的點有兩個:

  • 因為比較大包,需要額外的下載時間
  • 因為比較大包,瀏覽器載入這段 code 會花比較多時間

那麼接下來就來幫它瘦身吧!

瘦身123

首先,這次的專案本身是一個 Gatsby 網站,也就是說我們有強力的 webpack bundler 做後盾。

我們可以透過 webpack 包出一個只有我們需要的功能的 D3 object。

首先需要檢視我們到底用了哪些 D3 的 package 裡的功能,先看一眼網站內的效果:

d3 piechart with effect

用到的 package 如下:

  • d3-selection 基本選取操作工具,這是必備的
  • d3-transition 看到動畫表示有用到
  • d3-ease 看到比較炫砲的動畫表示有用到: 特殊 timing function 的 transition 總是會炫砲一點
  • d3-interpolate 看到比較炫砲的動畫表示有用到: 有嘗試自訂 interpolation 表示對動畫比較有追求,通常也會呈現比較炫砲的效果
  • d3-shape 看到複雜 SVG 圖形表示有用到: 複雜 SVG 圖形指的是像圓餅圖的切片這種需要用 <path> 才畫得出來的

當然,純看效果是沒有辦法 100% 兜出需要的 package 的,畢竟最懂用了什麼的人是寫這段 code 的我,所以上面這種純推論的伎倆其實是瞎掰的。

我是看 code 來找出用了哪一些 package 的,欸嘿 థ౪థ

心法

一眼看出使用什麼 package 的大原則就是

對照一下 d3.xxx()D3 API Reference 的哪個 package 區塊裡面。

舉例來說,我們看一下這段 code (可以不用嘗試理解它,隨便看看):

const svg = d3.select(elmRef.current).attrs({
  width,
  height,
  viewBox: `0 0 ${width} ${height}`,
  'shape-rendering': 'geomatricPrecision',
})

const group = svg.append('g').attr('transform', () => {
  let x = width / 2
  let y = height / 2
  if (withDescription) {
    if (lang === 'ja') {
      x += 0.05 * x
    } else {
      x += 0.1 * x
    }
  }
  return `translate(${x}, ${y})`
})

const pieGenerator = d3.pie().value(d => d.value.value)
const dataReady = pieGenerator(
  _.orderBy(d3.entries(data), ({ value }) => value.value, 'desc')
)
const durations = dataReady.map(
  d => (d.endAngle - d.startAngle) * ANGLE_SPEED
)
const delays = durations.map((t, i) => {
  if (i === 0) {
    return 0
  }
  return _.sum(_.slice(durations, 0, i))
})

const arcGenerator = d3
  .arc()
  .innerRadius(0)
  .outerRadius(radius)

套用大原則,可以用 d3.xxx() 對應出 package:

  • d3.select -> d3-select
  • d3.pie -> d3-shape
  • d3.entries -> d3-collection
  • d3.arc -> d3-shape

然而事情往往沒有這麼簡單,D3 鏈式操作的特性,使得其實有些 package 幾乎沒有用到 d3.xxx() 這種形式的呼叫,而是針對特定的物件(通常是 selection)做 prototype 的擴充。

D3 API Reference#d3-transition 就是一個很好的例子:

image-20191220013733567

實際使用的時候大都是:

d3.select(this)
  .transition()
  .ease(d3.easeLinear)
  .duration(duration)
  .delay(delay)

此時,儘管沒有用到 d3.xxx() 類的呼叫,還是需要記得打包要算上 d3-transition

場外加映一下 d3-transition 裡面是怎麼寫的

image-20191220014237461

經過一番嚴謹的判斷,我把有用到的部分重新 export 成一個 d3

/* file: d3.js */

export { select, selectAll } from 'd3-selection'

export { transition } from 'd3-transition'

export {
  easeElasticOut,
  easeSinOut,
  easeBackOut,
  easeLinear,
  easeCubicOut,
  easeExpOut,
} from 'd3-ease'

export { arc, pie } from 'd3-shape'

export { entries } from 'd3-collection'

export { interpolate, interpolateNumber } from 'd3-interpolate'

最後再把原本從 global 抓的 d3 換成 import 這個自己打包的版本就好啦!

修羅場

只是打包完畢並不能夠滿足我們旺盛的好奇心。

跟應有盡有的版本相比,到底變小了多少呢!?

讓我們親手來比較看看吧!

D3 的 d3/d3 repository 就是官方版打包用的 repository,話不多說,馬上 fork 一個來打包。

Fork 之後,小修改一下,在官方完整版之外加入我們的 custom 版本:

  • 新增 custom.js 作為 custom build 的進入點
  • 新增一個 rollup-custom.config.js 官方版是用 rollup 打包,比較適合打包 library
  • 修改 npm script 的 pretest,加入執行我們這個新的 rollup config

最後執行

# 安裝 node modules
yarn
# 開始 bundle
yarn pretest

然後我們就可以在 dist 資料夾裡面看到成果(方便視覺上比較,我把 *.node.js 砍了):

image-20191220020833748

同時也來比較一下實際載入花的時間:

evaluate-time

總結比較結果:

  • 檔案大小差距 242KB vs 50KB -> 瘦身後,大小減少了約 80%
  • 執行時間差距 58ms vs 16ms -> 瘦身後,載入 library 時間減少了約 72%

後記

是說最近我打算把方格子也加入我發表文章的平台。

畢竟我不喜歡大家一股腦往 Medium 發中文文章的模樣,如果不做點什麼來表現我堅定的立場,好像說不太過去。

這篇就是第一篇同步在方格子跟我的 blog 上架的文章。

考慮到方格子的向性,我也打算順便寫一些別的有的沒的(?),拓展一下寫文的觸角,看看有沒有什麼新的火花。最近看了紫羅蘭永恆花園之後,深刻感受到文字的力量之大,同時也覺得沒辦法好好運用文字的自己有點可悲 😢

稍微總結一下在方格子上的計畫:

  • https://pymaster.tw 同步發文
  • 除了前端專題,可能會再開一個動畫心得專題
  • 提高產文頻率,希望一週兩篇以上前端專題,動畫心得的話就看心情囉

希望往後我的文字能夠更加隨心所欲、Sincerely

👉 我的方格子個人頁面

Chrome 79 - 遲到了 6 年的 rem/em 修正

December 01, 2019

眼看 Chrome 79 大概再兩周就要 release 啦!

image-20191204101404584

對大多數人來說,這個也就是 Chrome 例行更新的一個版本。

而對我,Chrome 79 修正了一個最近一年多在我心裡佔有一席之地的 bug,我曾經努力試著解決它,最後似乎也找到自己應對它的方法。而現在它要被修掉了,多少有點感觸 😥。

本文特別想趕在 Chrome 79 正式釋出之前寫出來,讓在閱讀的你也可以一同經歷這個變化的過程。

話說從頭,可能會有點長,如果想直接看結論可以點這邊

Chrome 的「最小字體」傳說

我想小有接觸前端的人大概都知道 Chrome 有個 最小字體是 12px 的傳說,然而時過境遷、滄海桑田,2019 的現在還是如此嗎?

實際上現在再去 Google “chrome minimum font size” 得到的結果跟討論大都是 2013 年以前的內容,就算有稍微近期的文章,也大都是參考早期文章寫出來的。

從搜尋結果裡面可以找到這個 issue:

https://bugs.chromium.org/p/chromium/issues/detail?id=36429

裡面提到幾件重要的事情:

  • Chrome 預設的最小字體是和 UI 使用的語言有關的
  • 大部分的 UI 語言預設最小字體是 1px
  • 複雜 UI 語言(多為亞洲語言)的預設最小字體是 10px/12px
  • 2010年時還沒有辦法從設定該改最小字體的設定

而到了 2019 的現在,最小字體已經可以從設定裡面更改了,位置就在「設定」->「自訂字型」

image-20191201151933781

可能很多人會是在看到這篇文章之後才第一次打開這個設定畫面,比對一下上面列出來的項目可以發現繁中 UI 的 Chrome 的最小字體預設值就是 12px。

我猜如果從安裝時就是英文版 UI,預設值應該會是 0px/6px。

曾經是傳說的最小字體 12px,僅限於某些 UI 語言(基本上就是中文、日文、韓文、泰文)的瀏覽器。

現在其實是使用者把它調低就可以解決的問題。當然,對於開發者來說還是一個無法跨越的障礙。

做一個 fiddle 來測試一下「沒調設定」跟「調過設定」的結果: https://jsfiddle.net/pc035860/jgtfdL98/4/

image-20191201155500430

Twitch 聽說你是 rem 排版大師?

自從近年各瀏覽器對 rem 的支援度有提高之後,坊間出現一些使用 rem 為單位進行網頁排版的文章,並且漸漸也有些網站開始使用這種排版方法(雖然我覺得還是相對蠻少的)。

建議往下看之前要先瞭解一下用 rem 單位進行排版大概是怎麼樣進行,可以參考下面這篇 sitepoint 的文章 https://www.sitepoint.com/understanding-and-using-rem-units-in-css/

普遍常見的起手式,是把 rem base 設為 1px 或 10px,這樣一來直接使用 rem 單位換算的過程會簡單很多。

其中又以在 <html> 上用 font-size: 62.5% 來設定為 10px 最常見:

html {
  /* 16 * 0.625 = 10 */
  font-size: 62.5%;
}

Twitch 的大改版衝擊

Twitch 在 2018 年 2 月左右做了一次重大改版,把原本用的 Ember.js 換成 React,這一次改版同時也把網站的排版單位改為 rem,而這也是我們繁中 Chrome 使用者的噩夢開端(好像講得有點誇張了)。

改版之後,原本頻道的資訊頁面變得非常奇怪。下面示範截圖是目前最新版,當然也還是用 rem 單位排版。

正常來說應該是這樣:

image-20191201163414370

改版後就直接跑掉:

image-20191201163641701

這還只是第一眼就覺得不對勁的部分。

用 Firefox 打開 Twitch 來比較就可以看出整個網站都有點不一樣,Chrome 整體似乎大了一號,這邊就單取側邊欄來對比:

diff

WTF!? 不是, Twitch 難道沒有測過嗎…

Read on 

週末長知識: Google的彈指特效小研究

April 28, 2019

隨著「復仇者聯盟:終局之戰」的上映,Google 在和薩諾斯與無限手套相關的搜尋結果頁面加了一個灰飛煙滅的特效:

google_thanos_effect

特效被發現後引起了各界熱烈的討論,所以這週末的長知識就來蹭個熱度,看一下灰飛煙滅的特效是如何完成的。

將元素轉換為圖片

其實這個特效很直覺就認為有用 html2canvas,因為需要作出後續的類粒子特效,明顯需要是用圖片下去操作。

稍微用 Chrome devtools 追查一下之後就可以看到 html2canvas 的蹤跡:

1556430899758

1556430910104

粒子效果

如果在特效運作的時候觀察一下 DOM 的狀態,會發現似乎有大量的 canvas 被塞進來。深入追查特效本體的區塊,不像一般的粒子特效會有比較多粒子相關的運算,Google 這個特效的程式碼相對簡潔。

1556431244196

這個粒子效果其實是用一個比較平民的方法達成: 用 canvas 層層疊 + CSS transition 。

在動畫進行中的 Layer view 截圖是長這樣:

螢幕快照 2019-04-26 下午5.42.58

層層疊的學問

已經知道了原理是層層疊起來,是不是後面就簡單了?

仔細看一下上面的動畫截圖,可以發現其實它是稍微有 左 -> 右 這樣散開的傾向,而這點其實是需要靠一些巧思來達到的:

  • 多層 canvas 分開負責由左至右的像素
  • 用不同的 transition-delay 來讓動畫由左至右完成
  • 為了粒子效果的隨機性,對原圖取樣的時候也要加入隨機成分

取樣的方法

取樣的方法一樣是從程式碼裡面挖出來:

對所有像素 x, y:
  重複 n 次:
    layer = 隨機選出哪一層(x 值越靠前,層數越靠前)
    完整複製像素到 layer
  • 圖層疊起來一定可以拼出原本的圖
  • n 可以控制視覺上的紮實度,Google 取 2 看起來剛好

用此方法做一維取 8 層,大概會是像這樣:

1556436479412

幾層比較好?

看 code 可以發現 canvas 的數量就是 32 層,照上面的幾點推論下來,理論上層數越多看起來會越自然,當然也相對的越吃效能。

32 層大概是他們權衡效果與效能之後取得的一個最佳數字。

效果重現

把 Google 的版本整理一下,並且讓層數變成可以設定的參數,歡迎大家玩看看不同層數的效果。

如果想要知道對原圖取樣的方法,這個版本的程式碼應該也比打亂過的原版好讀很多。

注意1:使用手機觀看的話,層數過高瀏覽器會直接 crash

注意2:選用的 html2canvas 版本在 Firefox 上的繪製效果會有較大誤差,建議用其他瀏覽器觀看

後記

好久沒寫 blog,這次回來寫有一種力不從心的懶散感 Orz

再接再厲。

TGDF 2016: 第二天

July 06, 2016

7/1, 7/2 因緣際會的去了兩天的 2016 台北遊戲開發者論壇 (Taipei Game Developer Forum),雖然現在並沒有在開發遊戲,但兩天下來感覺相較於去技術意味濃厚的 conference ,收獲也是不少。

本系列文以簡單心得的方式做一些紀錄,免得以後忘光光了。一共分為兩天。

Indie MEGABOOTH: Best practices for showing your game to the public

講者: Kelly Wallick & Christopher Floyd

這場主要就是在介紹 “Indie MEGABOOTH” 這個組織在幹嘛(他們也帶了一些遊戲來參加外面的 indie space),怎麼樣可以幫助獨立遊戲開發者們在遊戲展上認識自己的遊戲,如果有興趣要如何申請,審核的流程又是如何等等的…

創辦人說她一年玩 500 款遊戲 (WTF)

據他們的說法,MEGABOOTH 在大型遊戲展的租攤位大小是比 AAA 廠商還大的,最近一次好像展出了 80 款獨立遊戲 (80個小 booth)。

Cross platform VR development – Building for multiple platforms with Job Simulator

講者: Alex Schwartz

又是 VR 場,而且還是 keynote

這場又有提到了昨天提到過的 90hz、Unity 等等,看來 Unity 確實是一時之選,疑似有支援講題中提到目前的三家主力 VR 廠商:

  • HTC Vive
  • Oculus Rift
  • PS VR

這場講題本身有 focus 在多平台之間產生的問題,發現把上面這三家攤在檯面上一比就知道 HTC Vive 看起來真的戰力超群。

感覺 VR 互動是一個重要的課題,然而 Oculus Rift 或 PS VR 在這方面的限制讓它們的遊戲類型可能會有所限制(或著遊戲在此平台被迫限制)。

控制器的部分三個平台都差不多,所以可以透過他們自己加掛的 library (OwlchemyVR) 很好的抽象化掉;然而控制器的位置 tracking 在不同平台的限制卻非常惱人 www

有興趣深入了解一下的話可以看一下這段影片,TGDF 本身應該是沒有錄影,這是之前同主題在別的地方的 talk,篇幅比較短一點的版本。

另外印象比較深的是QA階段有人提問「如果因為效能的問題需要在 90fps 以及 8x MSAA 之間做取捨,你會怎麼選擇?」,講者表示 __當然是會犧牲反鋸齒,不過如果有第三個選項,他寧可減少畫面上的物件數,也要盡可能保持 90fps + 8x MSAA。

相關的 performance 部分可以看這段

Flying by the Seat of Our Pants

講者: Raj Joshi

這場感覺講者之前的經歷非常厲害,待過各種大型遊戲公司,又學了非常多東西。

在過去 15 年間,Raj Joshi 曾從事派拉蒙影業、索尼影業的電影美術,以及遊戲公司動視、美商藝電與 Double Helix 的遊戲監製與製作人等工作

他疑似覺得膩了之後,受到一個之前認識的朋友邀請,賣掉家產(?)加入現在的獨立遊戲工作室,擔任一款新遊戲《GALAK-Z》的總監。

總之,就是談談各種中間碰到的慘事,以及最後如何熬過來,成功讓遊戲發行了。

口條非常之清晰,也偶爾會穿插一些笑點,不愧是總監。

話說他們的工作室預計是要 base 在大阪,似乎是因為創辦人娶了日本太太 www 他們整個 team 好像都是西方人啊~~~

傳遞感動怎麼可以這麼難?─《OPUS 地球計畫》的開發與發行經驗分享

講者: 李思毅

最近比較有名(連我都聽過的意思)的一款台灣製獨立遊戲,這場超滿的!

這款遊戲從構想初期就以傳遞感動為訴求,會選擇天文主題也是由於想要在設定上給予一種浩瀚 vs 渺小的感覺。

不過後來根據各種市場調查,感覺重點應該還是要有好的故事 XD

少見的開發時間比較短的遊戲(半年),其他場往往都是不小心就又多做了一年…

最認同的部分是「認真做一款遊戲,帶給玩家 2 小時的感動體驗,而不要求營運/課金」的精神。

《說劍》開發始末

講者: 陳禮國

講者很愛看武俠小說,一直夢想要做一款武俠遊戲,能夠傳達那種武俠的精神,最後在各種精煉下做出了這款《說劍》。

精煉的過程大概是這樣: 武俠 -> 刀劍 -> 手勢體驗

而劇情則是小說式的精煉: 劇情 -> 插圖 + 文字 -> 玩家自行腦補

看完遊戲玩法確實能夠感受到「武俠」的精神在裡面,非常厲害。

有興趣可以看一下介紹影片: https://www.youtube.com/watch?v=tDGEzNMqma8

系列文後記

這兩天都早起,前往會場的路上,一度微微感覺後悔接受友人的邀約(X)。不過事實證明我錯了,覺得兩天下來有充分的感受到「想做遊戲」的能量,有一種神祕的充電感(雖然我不做遊戲啦)。

我想說的是

有對遊戲充滿熱情的各位真是太好了。

那麼最後我們就來聽一首「廁所女神」吧!

TGDF 2016: 第一天

July 04, 2016

7/1, 7/2 因緣際會的去了兩天的 2016 台北遊戲開發者論壇 (Taipei Game Developer Forum),雖然現在並沒有在開發遊戲,但兩天下來感覺相較於去技術意味濃厚的 conference ,收獲也是不少。

本系列文以簡單心得的方式做一些紀錄,免得以後忘光光了。一共分為兩天。

「矛與盾」-獨立團隊自研與自營運間的拉扯

講者: 吳以尋

講的是「境界之詩」這款遊戲從製作、發行到營運碰到的問題,以及各種的掙扎。

令人眼睛為之一亮的點在,他們營運時選擇了比較細水長流的做法,而不是快速的把玩家榨乾(活動推到某個營業額就不再推活動)。並且也已經上線營運了一年左右的時間,難能可貴!

註: 其實現場大部分的遊戲都不是 in-app purchase 的營運向,所以「境界之詩」算是整個 TGDF 2016 中十分少見

Working with Google Play in an Evolving Ecosystem

講者: Kevin Chiao

看起來是贊助商的 Keynote,主要介紹 Google Play 各種充滿戰力的功能。

整個聽他介紹完覺得 Google Play 還真的是有夠用心,現在的 Google Play console 真是讓我大開眼界。

最有趣的是 A/B test 的功能示範了幾家公司測試 App icon 的成果,下載量實在提升太多了 ww

講者的中文腔調超重,我跟同行友人一致覺得他不如直接講英文,大家聽了比較舒暢。

Design and Emotions of VR Experiences

講者: Eddie Lee

主要是在講如何提升 VR 互動的層次。

講者是獨立遊戲工作室 Funktronic Labs 的創辦人,感覺對 VR 互動非常有研究,不過 VR 也還在發展階段,自然還是有一些尚在研究的問題。

他們最近主打的 VR 塔防遊戲 Cosmic Trip (game play),真的是相當精緻(相較於大部分的 VR demo)。

這場首次提到了之後在 VR 場會常聽到的幾個詞兒

  • 90hz (90fps) <- VR 讓使用者體感無壓力的的 fps 要求是穩定 90
  • Unity (5.4) <- 超多人用 Unity,5.4 好像有了某種非常強勁的 performance 更新

演講中提到在基地間移動是使用 VR 手把帶出一個類似傳送門的裝置再使用,於是在 QA 階段也有人問對於目前如果想要實際步行到某處該如何製作,這種所謂的 local motion 問題,現階段似乎還沒有完美的解決方案。

淺談美術風格統合;《Sdorica》開發經驗分享

講者: TEE

雷亞未推出的新作 RPG 《Sdorica》的美術總籌來分享 Sdorica 是如何嘗試定下現在的風格

  • 目標是東西方通吃
  • 決定韓系
  • Q版特色

現場有展示了一些場景畫面還有戰鬥人物動畫等等,真的是非常精緻/生動。

QA 時間有人問到戰鬥人物動畫是採用什麼方式製作的,TEE 表示是用 spine + sprite animation。

遊戲藝術的戰場前線─「概念設計」

講者: 陳以樂

講者負責的是團隊中製作遊戲概念設計,總之就是把需求轉為各種可能,並且對於發出需求方也有一些回饋,進而為遊戲發展出更多創意。

主要以他口中所謂的某款「武器娘」遊戲作為範例,有展示了許多發想階段的手稿,還算是有趣;不過因為這場接在 Sdorica 後面,武器娘的美術風格跟平常外面看到的遊戲也就是差不多的感覺,不像 Sdorica 有種眼睛一亮的 fu。

Mutation Mechanics: Mushroom 11 Tech

講者: Itay Keren

在我第一次 survey 議程表之前沒有聽過 Mushroom 11 這款遊戲,本來想說如果確定要聽這場的話應該先去 Steam 上買來玩一下,結果最後沒時間就作罷。

Mushroom 11 是一款相當特別的 platformer 遊戲,透過你的滑鼠可以消除真菌集合體的部分來達到各種神奇的效果,進而通關。

可以先看一下 gameplay

這場比較注重技術實作的部分,雖然沒有特別提是用什麼做的,但看來(又)是 Unity

  • mushroom physics
  • groups
  • background
  • camera
  • stage generation

這場聽完的感想完全就是這遊戲還真是有夠難做… 可能近期會買來膜拜一下 ww

Optimization 相關的部分,基本上載入關卡(隨流程動態)之後所有畫面上的東西都是 pool 住的,所以不會有 memory 的變動;接著會趁使用者 idle 的時候做 “replenish” 的動作,總之就是 idle 的時候就算小掉 fps 也還是沒有人會發現的。

神奇的是這款遊戲看起來就像是為了觸控而設計的遊戲,但它直到今日還沒有推出行動版遊戲,作者表示還在製作中。我想行動裝置上的種種硬體限制應該給它造成了相當的困境吧!

其他

  • 會議區外面的 Indie Space 獨立遊戲區 蠻有趣的
  • 沒有提供午餐只好去吃老朋友甜不辣
  • 下午茶看起來戰力超群
  • 感覺參加者大學生好多
  • 其實台北文創六樓這個空間還不錯
  • 這天九樓剛好是周杰倫的電競隊辦公室開幕