[Tracker] Scroll-depth tracking still forces layout on large pages after #6174
メンテナーはふだん 1 日以内に返信
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 18/100
- issue の種類
- バグ
- 明瞭さ
- おおむね明確
- 活発さ
- 停滞
- 技術スタック
- javascript
- 領域
- frontend, performance
調査の方向性
The scroll-depth code is the minified functions E, L and S in the published plausible.js: S wires a ResizeObserver on document.documentElement and a scroll listener on document, and E and L do the layout reads. Linked PR #6765 is already open, so check it before starting. Done means the reads no longer force layout inside a frame, or the scroll-depth tracking can be turned off through init.
索引モデルが issue の本文から書いたものです。
説明
Past Issues Searched
- I have searched open and closed issues to make sure that the bug has not yet been reported
Issue is a Bug Report
- This is a bug report and not a feature request, nor asking for self-hosted support
Using official Plausible Cloud hosting or self-hosting?
Plausible Cloud from plausible.io
Describe the bug
This is what remains of #5727 after the fix in #6174. With @plausible-analytics/tracker 0.4.6 (npm), the scroll-depth measurement still forces synchronous layout, in two places:
- The
ResizeObserverondocument.documentElement, whose callback readsscrollHeight,offsetHeightandclientHeighton bothbodyanddocumentElement. - The
scrolllistener ondocument, which readswindow.scrollYandwindow.innerHeight.
When layout is dirty at the time these run, each read makes the browser complete a full layout right then. On a small page that is negligible. On a page with a very large DOM it blocks the main thread for hundreds of milliseconds.
What we measured
We were investigating intermittent freezes on a long list page in our app (about 180,000 DOM elements). We recorded frames with the Long Animation Frames API (PerformanceObserver with type: 'long-animation-frame'), which attributes the time in each frame to individual scripts and reports forcedStyleAndLayoutDuration per script.
| Frame | Total blocked | Tracker's share |
|---|---|---|
| First frame after load | 1,391 ms | ResizeObserverCallback: 670 ms, of which 645 ms forced style and layout |
| A frame while scrolling | 142 ms | document.onscroll: 71 ms, of which 61 ms forced style and layout |
Both entries pointed at the tracker's code in our bundle. In the published plausible.js it is this:
function E(){var e=document.body||{},t=document.documentElement||{};return Math.max(e.scrollHeight||0,e.offsetHeight||0,e.clientHeight||0,t.scrollHeight||0,t.offsetHeight||0,t.clientHeight||0)}
function L(){var e,t;return e=window.innerHeight,t=window.scrollY,i<=e?i:t+e}
function S(){i=E(),o=L(),new ResizeObserver(function(){i=E()}).observe(document.documentElement),document.addEventListener("scroll",function(){var e=L();e>o&&(o=e)})}
The page renders items lazily as they approach the viewport, so the document's height changes often, and each change fires the ResizeObserver again.
We did not identify what leaves layout dirty before the tracker's callback runs. Other ResizeObserver callbacks in the same delivery round, or writes earlier in the frame, would do it. The cost lands in the tracker's callback because it is the one that reads.
The tracker is initialised with init({ domain, autoCapturePageviews: false, captureOnLocalhost: false, customProperties }).
Expected behavior
Scroll-depth bookkeeping should not force layout inside a rendering frame, or it should be possible to turn it off.
Some options:
- Defer the reads to a point where layout is already clean, for example
requestIdleCallbackwith a timer fallback, and coalesce repeated calls. The values are only sent with the engagement event, so they don't need to be exact within a frame. - In the
ResizeObservercallback, use the sizes on the delivered entries where possible, instead of re-readingscrollHeightand the others. - Add an
initoption to turn engagement or scroll-depth tracking off, which several people also asked for in #5727.
As a workaround we wrap init() so that, for the duration of that call, ResizeObserver and document.addEventListener('scroll', …) hand the tracker callbacks that are deferred to idle time. It relies on patching globals around a call into the library, so we'd rather not keep it.
Screenshots
No response
Environment
- OS: macOS
- Browser: Chrome
- Browser Version:
- Tracker: @plausible-analytics/tracker 0.4.6 (npm package)
- 主要言語
- Elixir
- スター
- 29.7k
- フォーク
- 1.9k
- 平均マージ
- 3日 7時間
- マージ済み PR(30日)
- 50
環境構築
- Dockerfile または Docker Compose ファイルあり
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
plausible/analytics のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
plausible/analytics#6802 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
ObanErrorReporter purges imported data when the cleanup worker crashes — silent loss of completed CSV imports対応中かも @EmmS21 が 99 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 74/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
plausible/analytics#5201 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
plausible/analytics#5200 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
plausible/analytics の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
-
[Python] Worker.close() spins at 100% CPU when its teardown task is canceled対応中かも @Glutexo が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
taskforcesh/bullmq#5053 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
hexlet-codebattle/codebattle#2374 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
mathieuprog/tz#44 ·
-
documentation
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
メンテナーはふだん 1 日以内に返信