diff --git a/.editorconfig b/.editorconfig index d5967b6..5f27700 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,19 +1,9 @@ -# Copyright (c) 2018 The Polymer Project Authors. All rights reserved. -# This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt -# The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt -# The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt -# Code distributed by Google as part of the polymer project is also -# subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt - -# Polymer EditorConfig - root = true [*] +end_of_line = lf +insert_final_newline = true charset = utf-8 indent_size = 2 indent_style = space trim_trailing_whitespace = true - -[*.md] -trim_trailing_whitespace = false \ No newline at end of file diff --git a/.gitignore b/.gitignore index 78bfc12..43558ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ -bower_components node_modules -yarn.lock -.vscode \ No newline at end of file +.vscode diff --git a/DESIGN.md b/DESIGN.md deleted file mode 100644 index 28e4544..0000000 --- a/DESIGN.md +++ /dev/null @@ -1,271 +0,0 @@ -# Virtual Scroller pieces - -This document gives an overview of various pieces we use to build up the -`` element. For now we are considering these implementation -details. A future proposal may expose these building blocks more directly, but -only after significant refinement. - -## VirtualRepeater - -- Orchestrates DOM creation and layouting, ensures minimum number of nodes is - created. -- Given a `totalItems` amount, it displays `num` elements starting from `first` - index. -- Delegates DOM creation, update and recycling via `createElement, - updateElement, recycleElement`. -- Delegates DOM layout via `_measureCallback`. - -### Basic setup - -```js -const repeater = new VirtualRepeater({ - /** - * Total number of items. - */ - totalItems: myItems.length, - /** - * From which index to start. - */ - first: 0, - /** - * How many items to render. - */ - num: 5, - /** - * Where to render the items. - */ - container: document.body, - /** - * The DOM representing data. - */ - createElement: (index) => { - const child = document.createElement('section'); - child.textContent = index + ' - ' + myItems[index]; - return child; - } -}); -``` - -### Recycling - -You can recycle DOM through the `recycleElement`, and use the recycled DOM in -`createElement`. - -If you decide to keep the recycled DOM attached in the main document, perform -DOM updates in `updateElement`. - -```js -/** - * Used to collect and recycle DOM. - */ -const pool = []; -const repeater = new VirtualRepeater({ - container: document.body, - /** - * The DOM representing data. - */ - createElement: (index) => { - return pool.pop() || document.createElement('section'); - }, - /** - * Updates the DOM with data. - */ - updateElement: (child, index) => { - child.textContent = index + ' - ' + myItems[index]; - }, - /** - * Invoked when the DOM is about to be removed. - * Here we keep the child in the main document. - */ - recycleElement: (child, index) => { - pool.push(child); - } -}); - -/** - * Now, when we manipulate `totalItems, first, num` properties, - * the DOM will be recycled. - */ -repeater.totalItems--; -repeater.num = 5; -setTimeout(() => { - repeater.num = 2; -}, 1000); - -``` - -### Data manipulation - -VirtualRepeater will update the DOM when `totalItems` changes. For cases where -data changes while keeping the same `totalItems`, or a specific item changes, -you can use `requestReset()` to notify of the changes, or force `totalItems` -change. - -```js -/** - * Forces change. - */ -repeater.totalItems--; -repeater.totalItems++; -/** - * You can also use `requestReset()` to notify of changes. - */ -myItems[0] = 'item 0 changed!'; -repeater.requestReset(); -``` - -### Protected methods/properties - -#### _incremental - -Set to true to disable DOM additions/removals done by VirtualRepeater. - -#### _measureCallback() - -You can receive child layout information through `_measureCallback`, which will -get invoked after each rendering. - -```js -repeater._measureCallback = (measuresInfo) => { - for (const itemIndex in measuresInfo) { - const itemSize = measuresInfo[itemIndex]; - console.log(`item at index ${itemIndex}`); - console.log(`width: ${itemSize.width}, height: ${itemSize.height}`); - } -}; -``` - -## Layout - -Given a viewport size and total items count, it computes children position, -container size, range of visible items, and scroll error. - -```js -const layout = new Layout({ - viewportSize: {height: 1000}, - totalItems: 20, - /** - * Layout direction, vertical (default) or horizontal. - */ - direction: 'vertical', - /** - * Average item size (default). - */ - itemSize: {height: 100}, -}); -``` - -Apply changes by invoking `layout.reflowIfNeeded()`. - -It notifies subscribers about changes on range (e.g. `first, num`), item -position, scroll size, scroll error. It's up to the listeners to take action on -these. - -```js -layout.addEventListener('rangechange', (event) => { - const range = event.detail; - console.log(`update first to ${range.first}`); - console.log(`update num to ${range.num}`); -}); - -layout.addEventListener('itempositionchange', (event) => { - const positionInfo = event.detail; - for (const itemIndex in positionInfo) { - const itemPosition = positionInfo[itemIndex]; - console.log(`item at index ${itemIndex}`); - console.log(`update position to ${itemPosition.top}`); - } -}); - -layout.addEventListener('scrollsizechange', (event) => { - const size = event.detail; - console.log(`update container size to ${size.height}`); -}); - -layout.addEventListener('scrollerrorchange', (event) => { - const error = event.detail; - console.log(`account for scroll error of ${error.top}`); -}); - -layout.reflowIfNeeded(); -``` - -Use `layout.updateItemSizes()` to give layout more information regarding item -sizes. - -```js -// Pass an object with key = item index, value = bounds. -layout.updateItemSizes({ - 0: {height: 300}, - 4: {height: 100}, -}); -``` - -### Move range - -Use `viewportScroll(type: {top: number, left: number})` to move the range to a -specific point. - -```js -const el = document.scrollingElement; -el.addEventListener('scroll', () => { - layout.viewportScroll = {top: el.scrollTop}; - layout.reflowIfNeeded(); -}); -``` - -Use `scrollToIndex(index: number, position: string)` to move the range to a -specific index. - -```js -// Scroll to the 3rd item, position it at the start of the viewport. -layout.scrollToIndex(2); - -// Scroll to the 10th item, position it at the center of the viewport. -layout.scrollToIndex(9, 'center'); - -// Scroll to the 20th item, position it at the end of the viewport. -layout.scrollToIndex(19, 'end'); - -// Scroll to the 100th item, position it at the end of the viewport -// if we are scrolled above it already, otherwise position it to the start. -layout.scrollToIndex(99, 'nearest'); - -``` - -## VirtualScroller - -- Extends `VirtualRepeater`, delegates the updates of `first, num` to a - `Layout` instance. -- Exposes a `layout` property, updates the `layout.totalItems`, - `layout.viewportSize`, and `layout.viewportScroll`. -- Subscribes to `layout` updates on range (`first, num`), children position, - scrolling position and scrolling size. -- Updates the container size (`min-width/height`) and children positions - (`position: absolute`). - -```js -const scroller = new VirtualScroller({ - /** - * The layout in charge of computing `first, num`, - * children position, scrolling position and scrolling size. - */ - layout: new Layout(), - /** - * Where to render the items. - */ - container: document.body, - /** - * The total number of items. - */ - totalItems: myItems.length, - /** - * The DOM representing data. - */ - createElement: (index) => { - const child = document.createElement('section'); - child.textContent = index + ' - ' + myItems[index]; - return child; - } -}); -``` diff --git a/README.md b/README.md index 8a98549..fc9c08a 100644 --- a/README.md +++ b/README.md @@ -1,553 +1,51 @@ -# <virtual-scroller> +# `` -`` maps a provided set of JavaScript objects onto DOM nodes, -and renders only the DOM nodes that are currently visible, leaving the rest -"virtualized". - -This document is an early-stage explainer for `` as a -potential future web platform feature, as part of the [layered -API](https://github.com/drufball/layered-apis) project. The repository also -hosts a proof-of-concept implementation that is being co-evolved with the -design. - -The (tentative) API design choices made here, as well as the element's -capabilities, take inspiration from the [infinite list study -group](https://github.com/domenic/infinite-list-study-group) research. - -## Example - -```html - - - - - -``` - -By default, the elements inside the virtual scroller created in this example -will be `
`s, and will be recycled. See below for more on customizing this -behavior through the `createElement` and `recycleElement` APIs. - -Checkout more examples in [demo/index.html](./demo/index.html). +The `virtual-content` element manages the rendering state of its child nodes, removing those that would not be visible to the end user from the browser's layout and paint steps to improve the rendering performance of your page. ## API -### `createElement` property - -Type: `function(item: any, itemIndex: number) => Element` - -Set this property to configure the virtual scroller with a factory that creates -an element the first time a given item at the specified index is ready to be -displayed in the DOM. - -The default `createElement` will, upon first being invoked, search for the -first `