• This repository has been archived on 01/Oct/2021
  • Stars
    star
    1,997
  • Rank 22,386 (Top 0.5 %)
  • Language
  • License
    Other
  • Created about 6 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A built-in virtual scroller for the web platform

This repository hosts explorations for a new web platform feature, a virtual scroller control. The idea of a virtual scroller is to provide a scrolling "viewport" onto some content, allow extremely large numbers of elements to exist, but maintain high performance by only paying the cost for those that are currently visible. Traditionally, we say that the non-visible content is virtualized.

Current status

This project is no longer being actively developed. Instead we are pursuing primitives here.

Why a virtual scroller?

Virtualized content is a popular and important pattern on the web. Most content uses it in some form: the https://m.twitter.com and https://facebook.com feeds; Google Photos and YouTube comments; and many news sites which have an automatic "scroll to next article" feature. Most popular frameworks have at least one high-usage virtualization component, e.g. React Virtualized with ~290K weekly downloads. See the infinite list study group for more research on existing virtualization solutions, both on the web and in other platforms like iOS.

At the same time, virtual scrollers are complex and hard to get right. In fact, having a first-class experience with virtualized content is currently impossible, because browsers don't expose the right hooks: things like accessible landmark navigation, find in page, or intra-page anchor navigation are based solely on DOM structure, and virtualized content is by definition not in the DOM. Additionally, today's virtualized content does not work with search engine crawlers, which means that sites that care about search engine ranking are unable to apply this important performance technique. This is bad for the web.

We believe that, like native platforms, the web deserves a first-class virtual scroller implementation that works out of the box.

For more details on the motivation, see Motivation.md.

Sample code

<!--
  virtual-scroller lives in a built-in module and needs to be imported before use.
  (The name of the module is subject to change.)
-->
<script type="module">
import "std:virtual-scroller";
</script>

<!--
  The <virtual-scroller> will manage the rendering of its children.
  It will prioritize rendering things that are in the viewport and not render
  children that are far away, such that we are only paying as little rendering
  cost as possible while still allowing them to work with find-in-page,
  accessibility features, focus navigation, fragment URL navigation, etc.
-->
<virtual-scroller id='scroller'>
  <div>Item 1</div>
  <div>Item 2</div>
  ...
  <div>Item 1000</div>
</virtual-scroller>

<script>
// You can add, remove, modify children of the <virtual-scroller> as you would
// a regular element, using DOM APIs.
scroller.append(...newChildren);

// When the set of actually-rendered children is about to change,
// the <virtual-scroller> will fire a "rangechange" event with the
// new range of rendered children.
scroller.addEventListener('rangechange', (event) => {
  if (event.first === 0) {
    console.log('rendered first item.');
  }
  if (event.last === scroller.children.length - 1) {
    console.log('rendered last item.');
    // Perhaps you would want to load more data for display!
  }
});
</script>

Goals

  • Be flexible enough to be used for various kinds of scrolling, larger-than-viewport content, from news articles to typeaheads to contact lists to photo galleries.
  • Only pay the rendering costs for visible child elements of the scroller (with some margin).
  • Allow contents of the scroller to work with find-in-page, accessibility features, focus, fragment URL navigation, etc., just as they would in a non-virtualized context.
  • Be flexible enough to allow developers to integrate advanced features and behaviors, such as groupings, sticky headers, animations, swiping, item selection, etc.
  • Support 1D horizontal/1D vertical/2D wrapped-grid layouts.

Non-goals

  • Allow data that are not part of the DOM to work with find-in-page, accessibility, etc. The DOM remains the source of truth for these browser features, not e.g. the server-side, or other in-memory JavaScript data structures. See below for more elaboration.

And, at least in v1, the following are out of scope:

  • Built-in support for advanced features and behaviors, such as those mentioned above.
  • Support infinite-grid (spreadsheet-like), multi-column (content continues from bottom of col(N) to top of col(N+1)) or masonry layouts.

Proposed APIs

<virtual-scroller> element

The <virtual-scroller> element represents a container that will manage the rendering of its children. The children of this element might not get rendered/updated if they are not near or in the viewport. The element is aware of changes to the viewport, as well as to its own size, and will manage the rendered state of its children accordingly.

Its children can be any element. Semantically, the element is similar to a <div>, with the addition of focus on only the visible contents when the element overflows its container and is scrolled.

The sizes and other layout values of the non-rendered children, which might affect scrollbar height, etc., are approximate sizes and might not be always accurate. The rendered children always have accurate style and layout values, just like other normal DOM nodes.

All children, rendered or non-rendered, will work with find-in-page, focus navigation, fragment URL navigation, and accessibility technology, just like normal DOM nodes.

rangechange event

Fired when <virtual-scroller> is about to render a new range of items, e.g. because the user scrolled. This will fire at requestAnimationFrame timing, i.e. before the browser is about to paint.

The event has the following properties:

  • first: an integer, the 0-based index of the first children currently rendered.
  • last: an integer, the 0-based index of the last children currently rendered.
  • bubbles: false
  • cancelable: false
  • composed: false

As an example, this can be used to delay more costly rendering work. For example, a scrolling code listing could perform just-in-time syntax highlighting on lines right before they become visible, leaving the un-adorned code accessible by find-in-page/etc. but improving the code's appearance before the user sees it.

TODO: do we need first and last on the event, or should we just use the properties of the element?

rangeFirst and rangeLast getters

These return 0-based indices giving the first and last children currently rendered.

TODO: these names are kind of bad?

Constraints and effects

Ideally, we would like there to be zero constraints on the contents of the <virtual-scroller> element, or on the virtual-scroller element itself.

Similarly, we would like to avoid any observable effects on the element or its children. Just like how <select> does not cause its <option> elements to change observably when you open the select box, ideally <virtual-scroller> should not cause observable effects on its children as the user scrolls around.

This may prove difficult to specify or implement. In reality, we expect to have to add constraints such as:

  • Overriding the default values for certain CSS properties (and ignoring web developer attempts to set them).
  • Having degenerate behavior if visual order does not match DOM order (e.g. via flex-order or position: absolute).

And the control may influence its children via effects such as:

  • Changing the computed style of children (observable via getComputedStyle(child)).
  • Changing the display-locked status of children (observable via child.displayLock.locked).
  • Changing the layout of non-visible children's descendants (observable via e.g. child.children[0].getBoundingClientRect()).

Figuring out the exact set of constraints (including what happens when they're violated), and the exact set of effects, is a key blocker for standardization that we expect to address over time.

Use cases

Cases this proposal covers well

This design is intended to cover the following cases:

  • Short (10-100 item) scrollers. Previously, virtualizing such scrollers was done rarely, as virtualization caused sacrifices in developer and user experience. We are hopeful that with a first-class virtualization element in the web platform, it will become more expected to use <virtual-scroller> in places where overflow-scrolling <div>s were previously seen, thus improving overall UI performance.

  • Medium (100-10ย 000 item) scrollers. This is where virtual scrollers have traditionally thrived. We also want to expand this category to include not just traditional list- or feed-like scenarios, but also cases like news articles.

  • Large (10ย 000+ item) scrollers, where data is added progressively. As long as the data can be held in memory, <virtual-scroller> ensures that there are no rendering costs, and so can scale indefinitely. An example here would be any interface where scrolling down loads more content from the server, indefinitely, such as a social feed or a busy person's inbox.

    However, note that adding a large amount of data at once is tricky with this API; see below.

Very large amounts of initial data

Consider the cases of the singlepage HTML specification, or of long-but-finite lists such as a large company directory.

We believe that all these scenarios are still suited for use with this virtual scroller control. As long as the data could feasibly fit in memory in any form, the user experience will be best if it is stored in the DOM, inside a virtual scroller that makes the rendering costs of out-of-viewport items zero. This allows access to the data by browser features, such as find-in-page or accessibility tooling, as well as by search engines.

However, using the above API in these scenarios suffers from the problem of initial page load costs. Trying to server-render all of the items as <virtual-scroller> children, or trying to do an initial JSON-to-HTML client-render pass, will jank the page. For example, just the parsing time alone for the single-page HTML specification can take 0.6โ€“4.4 seconds. And there are staging problems in trying to deliver large amounts of HTML while the "std:virtual-scroller" module is still being imported, which could prevent it from properly avoiding initial rendering costs.

As such we think there is still room for improvement in these scenarios, e.g. with an API that makes it easy to progressively stream data during idle time to allow the initial few screenfuls to render ASAP and without jank. We will be exploring this problem over time, after we feel confident that we can specify and implement a solution for the core use cases.

Almost-infinite data from the server

A consistent point of confusion about the virtual scroller proposal is how it purports to solve cases like social feeds, where there is an "almost infinite" amount of data available.

This proposal's answer is that: if you were going to have the data in memory anyway, then it should be in the <virtual-scroller>, and thus accessible to the browser or other technologies (such as search engines) that operate on the DOM. But, if you were going to leave the data on the server, then it is fine to continue leaving it on the server, even with a <virtual-scroller> in play.

For example, in one session while browsing https://m.twitter.com/, it limited itself to only keeping 5 tweets in the DOM at one time, using traditional virtualization techniques. However, it appeared to have about 100 tweets in memory (available for display even if the user goes offline). And, when the user began scrolling toward the bottom of the page, it queried the server to increase the amount of in-memory tweets it had available. With a native <virtual-scroller> in the browser, which mitigates the rendering costs while still allowing you to keep items in the DOM, we're hopeful that it'd be possible to keep those 100+ tweets as DOM nodes, not just in-memory JavaScript values that are locked away from find-in-page and friends.

This proposed design does mean that there could be things on the Twitter servers which are not findable by find-in-page, because they have not yet been pulled from the server and into the DOM. That is OK. Find-in-page is not meant to be find-in-site, and users of social feeds are able to understand the idea that not everything is yet loaded. What is harder for them to understand is when they saw a phrase, they scroll past it by 100 pixels, and then find-in-page can't see it anymore, because it's been moved out of the DOM. <virtual-scroller> addresses this latter problem.

Alternatives considered

Using traditional virtualization

Previously, we intended to specify a traditional approach to virtualization for the built-in virtual scroller. With that approach, the element would map JavaScript values ("items") to DOM element children, putting only a small portion of the items in the DOM, with callbacks for creating, updating, and recycling the DOM elements given an item.

However, this approach suffers the same problem as existing traditionally-virtualized scrollers regarding accessibility, find-in-page, fragment URL and focus navigation, etc., all of which depend on having the content be part of the DOM to work correctly. This is a known issue with traditional virtualization, which web developers have to grapple with today, trading off these functionalities with the performance improvement. As we intend for the built-in virtual scroller to be a standard building block that a lot of web authors would use or build on, we don't want to continue having this disadvantage.

In other words, given the problem of too much DOM causing bad performance, traditional virtualization is managing the symptoms, by decreasing the amount of DOM. For a standard solution, we want to tackle the core problem head-on.

Find-in-page APIs

As mentioned in the previous section, we want to make features like find-in-page work with the built-in virtual scroller. We have briefly considered adding a set of find-in-page APIs to the web platform, that would support cases like giving the web author a way to completely override the find-in-page command, or interacting with and adding results to the user agent's built-in find-in-page functionality.

However, designing these APIs proved to be quite challenging, given the breadth of find-in-page user interfaces across browsers. Worse, providing a find-in-page-specific solution might unintentionally become a disadvantage for other things like accessibility: web developers might be inclined to think that a virtual scroller that works with find-in-page is good enough, and not think about the remainder of the missing functionality caused by virtualization.

Libraries

Another approach would be to standardize and implement only the low-level primitives which allow mitigating the cost of DOM, i.e. display locking. We would then leave the building of high-level virtual scroller APIs to libraries.

We fully expect that some applications and libraries will take this route, and even encourage it when appropriate. But we still believe there is value in providing a high-level virtual scroller control built into the platform, for the 90% case. For more on our reasoning, see the motivation document's "Standardization" and "Layering" sections.

Sample implementations

Chrome

Launch chrome with flags --enable-blink-features=DisplayLocking,BuiltInModuleAll to get a working virtual-scroller element.

Demos

https://github.com/fergald/virtual-scroller-demos

More Repositories

1

webcomponents

Web Components specifications
HTML
4,306
star
2

import-maps

How to control the behavior of JavaScript imports
JavaScript
2,636
star
3

focus-visible

Polyfill for `:focus-visible`
JavaScript
1,606
star
4

webusb

Connecting hardware to the web.
Bikeshed
1,289
star
5

webpackage

Web packaging format
Go
1,215
star
6

EventListenerOptions

An extension to the DOM event pattern to allow authors to disable support for preventDefault
JavaScript
1,166
star
7

portals

A proposal for enabling seamless navigations between sites or pages
HTML
945
star
8

floc

This proposal has been replaced by the Topics API.
Makefile
939
star
9

inert

Polyfill for the inert attribute and property.
JavaScript
914
star
10

scheduling-apis

APIs for scheduling and controlling prioritized tasks.
HTML
896
star
11

view-transitions

789
star
12

file-system-access

Expose the file system on the userโ€™s device, so Web apps can interoperate with the userโ€™s native applications.
Bikeshed
641
star
13

background-sync

A design and spec for ServiceWorker-based background synchronization
HTML
639
star
14

scroll-to-text-fragment

Proposal to allow specifying a text snippet in a URL fragment
HTML
577
star
15

ua-client-hints

Wouldn't it be nice if `User-Agent` was a (set of) client hints?
Bikeshed
575
star
16

aom

Accessibility Object Model
HTML
553
star
17

kv-storage

[On hold] A proposal for an async key/value storage API for the web
550
star
18

observable

Observable API proposal
Bikeshed
515
star
19

turtledove

TURTLEDOVE
Bikeshed
505
star
20

navigation-api

The new navigation API provides a new interface for navigations and session history, with a focus on single-page application navigations.
Makefile
474
star
21

webmonetization

Proposed Web Monetization standard
HTML
439
star
22

trust-token-api

Trust Token API
Bikeshed
412
star
23

attribution-reporting-api

Attribution Reporting API
Bikeshed
338
star
24

direct-sockets

Direct Sockets API for the web platform
HTML
304
star
25

shape-detection-api

Detection of shapes (faces, QR codes) in images
Bikeshed
299
star
26

display-locking

A repository for the Display Locking spec
HTML
294
star
27

background-fetch

API proposal for background downloading/uploading
Shell
279
star
28

resize-observer

This repository is no longer active. ResizeObserver has moved out of WICG into
HTML
256
star
29

first-party-sets

Bikeshed
255
star
30

serial

Serial ports API for the platform.
HTML
254
star
31

priority-hints

A browser API to enable developers signal the priorities of the resources they need to download.
Bikeshed
228
star
32

dbsc

HTML
227
star
33

sanitizer-api

Bikeshed
223
star
34

is-input-pending

HTML
222
star
35

proposals

A home for well-formed proposed incubations for the web platform. All proposals welcome.
209
star
36

spatial-navigation

Directional focus navigation with arrow keys
JavaScript
199
star
37

js-self-profiling

Proposal for a programmable JS profiling API for collecting JS profiles from real end-user environments
HTML
196
star
38

cq-usecases

Use cases and requirements for standardizing element queries.
HTML
185
star
39

interventions

A place for browsers and web developers to collaborate on user agent interventions.
178
star
40

visual-viewport

A proposal to add explicit APIs to the Web for querying and setting the visual viewport
HTML
176
star
41

frame-timing

Frame Timing API
HTML
170
star
42

layout-instability

A proposal for a Layout Instability specification
Makefile
157
star
43

isolated-web-apps

Repository for explainers and other documents related to the Isolated Web Apps proposal.
Bikeshed
154
star
44

page-lifecycle

Lifecycle API to support system initiated discarding and freezing
HTML
153
star
45

speech-api

Web Speech API
Bikeshed
144
star
46

cookie-store

Asynchronous access to cookies from JavaScript
Bikeshed
141
star
47

nav-speculation

Proposal to enable privacy-enhanced preloading
HTML
141
star
48

construct-stylesheets

API for constructing CSS stylesheet objects
Bikeshed
137
star
49

webhid

Web API for accessing Human Interface Devices (HID)
HTML
135
star
50

color-api

A proposal and draft spec for a Color object for the Web Platform, loosely influenced by the Color.js work. Heavily WIP, if you landed here randomly, please move along.
HTML
124
star
51

devtools-protocol

DevTools Protocol
JavaScript
120
star
52

fenced-frame

Proposal for a strong boundary between a page and its embedded content
Bikeshed
118
star
53

sms-one-time-codes

A way to format SMS messages for use with browser autofill features such as HTMLโ€™s autocomplete=one-time-code.
Makefile
109
star
54

bundle-preloading

Bundles of multiple resources, to improve loading JS and the Web.
HTML
103
star
55

netinfo

HTML
95
star
56

intrinsicsize-attribute

Proposal to add an intrinsicsize attribute to media elements
94
star
57

window-controls-overlay

HTML
94
star
58

manifest-incubations

Before install prompt API for installing web applications
HTML
92
star
59

container-queries

HTML
92
star
60

animation-worklet

๐Ÿšซ Old repository for AnimationWorklet specification โžก๏ธ New repository: https://github.com/w3c/css-houdini-drafts
Makefile
92
star
61

async-append

A way to create DOM and add it to the document without blocking the main thread.
HTML
87
star
62

privacy-preserving-ads

Privacy-Preserving Ads
86
star
63

indexed-db-observers

Prototyping and discussion around indexeddb observers.
WebIDL
83
star
64

shared-storage

Explainer for proposed web platform Shared Storage API
Bikeshed
82
star
65

compression

Standard text for CompressionStream and DecompressionStream API
HTML
81
star
66

file-handling

API for web applications to handle files
81
star
67

compression-dictionary-transport

80
star
68

canvas-color-space

Proposed web platform feature to add color management, wide gamut and high bit-depth support to the <canvas> element.
78
star
69

canvas-formatted-text

HTML
77
star
70

local-font-access

Web API for enumerating fonts on the local system
Bikeshed
75
star
71

performance-measure-memory

performance.measureMemory API
HTML
73
star
72

starter-kit

A simple starter kit for incubations
JavaScript
72
star
73

handwriting-recognition

Handwriting Recognition Web API Proposal
Makefile
72
star
74

css-parser-api

This is the repo where the CSS Houdini parser API will be worked on
HTML
72
star
75

ContentPerformancePolicy

A set of policies that a site guarantees to adhere to, browsers enforce, and embedders can count on.
HTML
72
star
76

web-app-launch

Web App Launch Handler
HTML
72
star
77

pwa-url-handler

71
star
78

eyedropper-api

HTML
70
star
79

idle-detection

A proposal for an idle detection and notification API for the web
Bikeshed
67
star
80

close-watcher

A web API proposal for watching for close requests (e.g. Esc, Android back button, ...)
Makefile
67
star
81

storage-foundation-api-explainer

Explainer showcasing a new web storage API, NativeIO
65
star
82

video-editing

64
star
83

uuid

UUID V4
63
star
84

client-hints-infrastructure

Specification for the Client Hints infrastructure - privacy preserving proactive content negotiation
Bikeshed
61
star
85

sparrow

59
star
86

element-timing

A proposal for an Element Timing specification.
Bikeshed
59
star
87

digital-credentials

Digital Credentials, like driver's licenses
HTML
55
star
88

local-peer-to-peer

โ†”๏ธ Proposal for local communication between browsers without the aid of a server.
Bikeshed
53
star
89

video-rvfc

video.requestVideoFrameCallback() incubation
HTML
53
star
90

time-to-interactive

Repository for hosting TTI specification and discussions around it.
52
star
91

digital-goods

Makefile
49
star
92

private-network-access

HTML
49
star
93

raw-clipboard-access

An explainer for the Raw Clipboard Access feature
45
star
94

document-picture-in-picture

Bikeshed
45
star
95

admin

๐Ÿ‘‹ Ask your questions here! ๐Ÿ‘‹
HTML
42
star
96

soft-navigations

Heuristics to detect Single Page Apps soft navigations
Bikeshed
42
star
97

pending-beacon

A better beaconing API
Bikeshed
40
star
98

webcrypto-secure-curves

Proposal for the addition of Curve25519 and Curve448 to the Web Cryptography API
HTML
40
star
99

entries-api

Spec defining browser support for file/directory upload by drag-and-drop
Bikeshed
40
star
100

transfer-size

38
star