テレビのアニメはどこから来る? / 第68話

第68話:イトは、更新処理をもう一度走らせなかった|DOMはいつ画面になる?

ピコルート第68話。更新済みのDOMと古い画面が食い違った。イトは処理を再実行せず、computed style、box、layout、paint、次のframeまでを追います。

レンダリングDOMレイアウトペイント10分更新

第67話の操作室で、イトは反応しないbuttonを二度押さなかった。

listenerを一つ登録し、requestを一回だけ送り、返事を確かめてから二つのDOM nodeを更新した。

操作記録は、きれいにそろっている。

handler invocation     1
request                1
accepted response      1
DOM update batch       1
updated DOM nodes      2
duplicate request      0

それなのに、出口の大画面にはまだ灰色の 「待機中」 が残っていた。

イトの指が、もう一度buttonへ伸びる。

「画面が変わってない。やっぱり、もう一回動かさないと」

DOMは新しい。画面は古い

ユイが、画面の横にある二つの窓を開いた。

左は、いまブラウザが持っているDOM。

右は、高速カメラが最後に記録した画面だ。

current DOM
  data-state = "ready"
  status text = "受信完了"

last captured frame
  data-state = "waiting"
  status text = "待機中"

ユイ

DOMの文字は、もう変わっています。でも、最後に写った画面は一つ前の状態です

マコト

同じページを見ているのに、記録した時点が違うのか

イト

DOMが変わったら、その瞬間に画面も同じになるんじゃないの?

大画面の上に、赤い数字がともった。

次の案内開始まで、18秒。

その案内では、見学者全員が大画面を見て次の入口を選ぶ。灰色の「待機中」が残れば、進めない。

ただし、requestをもう一度送れば、返事が二重になるかもしれない。

隠すだけなら、壊れた場所を見失う。

イトは、伸ばした指をbuttonの手前で止めた。

三つのレバー

作業台から、三つのレバーがせり上がった。

A:handlerをもう一度動かす

requestからDOM更新まで、同じ処理をもう一周する。画面が変わる可能性はあるが、すでに成功した処理まで重複する。

B:古い表示の上に「完了」の札をかぶせる

見た目だけは間に合う。しかし、DOMと画面が食い違った理由は分からない。

C:DOMは一回の更新のまま、次のframeまでを追う

style、boxと配置、描画、画面に出たframeを別々に記録する。どこまで新しい状態が届いたかを確かめる。

ピコは答えを言わなかった。三つのレバーの下へ、小さな注意書きだけを置いた。

DOMの変更と、画面に見えるpixelは、同じ観測地点ではない。

残り14秒。

イトは、さっきの記録を見返した。handlerもrequestもDOM更新も、すでに一回成功している。

「失敗した証拠がない処理を、見た目だけで繰り返したくない」

イトはCのレバーを下ろした。

一回の更新を、次のframeまで追う

作業台が暗くなり、一本の青い軌道だけが残った。

軌道の始点は、更新後のDOM。

終点は、見学者が実際に見る画面だ。

イトが再実行レバーから手を離し、一回だけ更新したDOMからstyle、box、描画、新しい画面frameへ進む青い軌道を追っている
処理を重ねず、すでに起きた一回のDOM更新がどの観測地点まで届いたかを追う。

最初の記録板には、枝分かれした構造が浮かんだ。

見出し。

状態を示す段落。

次へ進むlink。

HTMLをもとにブラウザが扱うこの構造が、DOMだ。

画面に文字が見える前から、DOMにはnodeとその関係がある。JavaScriptが今回変えたのは、このうち状態表示に関係する二つだけだった。

DOM batch             1
target nodes          2
extra DOM mutation    0

「ここまでは新しい」

イトは、始点へ青い印をつけた。

CSSは、誰に何を当てるか

次の記録板に、CSSのruleが流れ込んだ。

[data-state="ready"] .status {
  color: #126a55;
  font-weight: 700;
}

DOMにdata-state="ready"があるので、.statusへ新しい見た目の値が対応する。

ここで大切なのは、CSSの文字列をそのまま画面へ貼るのではないことだ。

どのruleがどのelementへ当たり、競合するruleのうち何が採用されたかを経て、そのelementで使う値が決まる。

この回の観測板では、それを computed style の地点として記録した。

computed color         updated
computed font-weight   updated
old style value        0

イト

DOMが新しくても、まずどの見た目になるかを確かめる地点があるんだ

ピコ

そう。DOMとCSSのruleと、画面で使われる値を一つの箱にしないのがコツだよ

DOMの枝が、そのまま四角になるわけではない

三つ目の記録板には、透明な四角が現れた。

statusの幅、高さ、x座標、y座標。

その外側のpanel。

隣のbuttonとの間隔。

ブラウザは、表示に使うboxを作り、大きさや位置を決めていく。

ただし、DOM nodeと画面のboxは一対一とは限らない。

見えない設定によってboxを作らないnodeもある。反対に、一つのelementが複数のboxに分かれることもある。

だからイトは、「DOMの枝をそのまま並べる」とは書かなかった。

ready status box       present
width / height         resolved
position               resolved
layout overflow        0

前話で変えた文字は少し長い。それでもpanelからはみ出さず、次のlinkも押せる位置に残った。

残り8秒。

描くことと、見せたframeを分ける

四つ目の記録板で、透明だったboxへ色と文字が重なった。

背景を描く。

文字を描く。

境界線を描く。

必要なら、重なり合う内容をまとめる。

この回では、それらを paint と composite の観測地点に分けた。

けれど、これは仕組みを見分けるための工房のtraceだ。実際のbrowserで、内部の仕事が必ずこの札どおりの回数や順番でlogへ現れる、という意味ではない。

ブラウザは、表示を更新できる機会に、必要な仕事を進める。何も変わっていない場所まで、毎回すべて同じように作り直すとは限らない。

paint record for status    1
composited result          ready
presented new frame        0

マコトが、最後の0を指した。

「描く準備は進んでる。でも、まだ新しいframeを見せていない」

高速カメラが古い「待機中」を写したのは、この直前だった。

DOM更新が失敗したのではない。

新しい表示を出す前のframeを、一枚だけ捕まえていた。

次の表示機会

残り5秒。

イトはbuttonへ戻らなかった。

新しいrequestも送らない。

DOMも変えない。

traceの終点にあるframe番号だけを見つめた。

captured old frame     417  waiting
next presented frame   418  ready

番号が一つ進んだ。

大画面の灰色が消え、深い緑の 「受信完了」 が現れた。次へ進むlinkも、同じframeで明るくなった。

案内開始まで、3秒。

見学者たちは迷わず次の入口を選んだ。

再実行しなかった結果

イトは、最初の操作記録とframe traceを一枚に重ねた。

handler invocation       1
request                  1
accepted response        1
DOM update batch         1
extra DOM mutation       0
computed style update    1
layout overflow          0
paint record             1
presented new frame      1
duplicate request        0

ユイ

画面が古い一瞬を、処理全体の失敗と決めなかったから、重複を増やさずに済みました

マコト

DOM、style、box、描画、見せたframe。『画面になった』を観測地点に分けられたね

イト

見た目が変わらないとき、最初からやり直す前に、どこまでは新しいのかを確かめる。今回は、DOMをもう一度変える問題じゃなかった

ピコは最後に、短い式を残した。

DOMが新しい
  ≠ その瞬間に撮ったframeも新しい

一回の更新を保つ
  + 観測地点を分ける
  = 重複させずに次のframeまで追える

画面は残ったのに、返事だけ来ない

案内が始まり、大画面は正しく描かれた。

そのとき、工房の外で光の線が一本消えた。

イトの端末には、いま描いた「受信完了」の画面が残っている。端末とWi-Fiルーターを結ぶ印も明るい。

ところが、次の時刻を取りに行ったrequestには、新しいresponseが返ってこなかった。

local screen         visible
Wi-Fi link           connected
new outside response none

イトは、今度はbuttonにもDOMにも触れなかった。

端末の中で画面になる仕事と、工房の外から返事が届く道は、同じ場所ではない。

「画面は見える。Wi-Fiもつながってる。それでも外から届かないなら、次はどの区間を見ればいい?」

ピコが扉を開くと、ルーターの先から世界中へ伸びる大きな地図が現れた。

次回、ピコルート第69話。

インターネットは何をつないでいる?

この物語を、解説で深める

第68話で生まれた疑問を、解説記事で少し深く見ていきます。物語で感じた不思議さを残したまま、言葉と仕組みを順番に整理しましょう。

レンダリングとは|ブラウザが材料を画面に描く処理を読む