react-canvas

Introductory blog post

React Canvas adds the ability for React components to render to <canvas> rather than DOM.

This project is a work-in-progress. Though much of the code is in production on flipboard.com, the React canvas bindings are relatively new and the API is subject to change.

Motivation

Having a long history of building interfaces geared toward mobile devices, we found that the reason mobile web apps feel slow when compared to native apps is the DOM. CSS animations and transitions are the fastest path to smooth animations on the web, but they have several limitations. React Canvas leverages the fact that most modern mobile browsers now have hardware accelerated canvas.

While there have been other attempts to bind canvas drawing APIs to React, they are more focused on visualizations and games. Where React Canvas differs is in the focus on building application user interfaces. The fact that it renders to canvas is an implementation detail.

React Canvas brings some of the APIs web developers are familiar with and blends them with a high performance drawing engine.

Installation

React Canvas is available through npm:

npm install react-canvas

React Canvas Components

React Canvas provides a set of standard React components that abstract the underlying rendering implementation.

<Surface>

Surface is the top-level component. Think of it as a drawing canvas in which you can place other components.

<Layer>

Layer is the the base component by which other components build upon. Common styles and properties such as top, width, left, height, backgroundColor and zIndex are expressed at this level.

<Group>

Group is a container component. Because React enforces that all components return a single component in render(), Groups can be useful for parenting a set of child components. The Group is also an important component for optimizing scrolling performance, as it allows the rendering engine to cache expensive drawing operations.

<Text>

Text is a flexible component that supports multi-line truncation, something which has historically been difficult and very expensive to do in DOM.

<Image>

Image is exactly what you think it is. However, it adds the ability to hide an image until it is fully loaded and optionally fade it in on load.

<Gradient>

Gradient can be used to set the background of a group or surface.

  render() {
    ...
    return (
      <Group style={this.getStyle()}>
        <Gradient style={this.getGradientStyle()} 
                  colorStops={this.getGradientColors()} />
      </Group>
    );
  }
  getGradientColors(){
    return [
      { color: "transparent", position: 0 },
      { color: "#000", position: 1 }
    ]
  }

<ListView>

ListView is a touch scrolling container that renders a list of elements in a column. Think of it like UITableView for the web. It leverages many of the same optimizations that make table views on iOS and list views on Android fast.

Events

React Canvas components support the same event model as normal React components. However, not all event types are currently supported.

For a full list of supported events see EventTypes.

Building Components

Here is a very simple component that renders text below an image:

var React = require('react');
var ReactCanvas = require('react-canvas');

var Surface = ReactCanvas.Surface;
var Image = ReactCanvas.Image;
var Text = ReactCanvas.Text;

var MyComponent = React.createClass({

  render: function () {
    var surfaceWidth = window.innerWidth;
    var surfaceHeight = window.innerHeight;
    var imageStyle = this.getImageStyle();
    var textStyle = this.getTextStyle();

    return (
      <Surface width={surfaceWidth} height={surfaceHeight} left={0} top={0}>
        <Image style={imageStyle} src='...' />
        <Text style={textStyle}>
          Here is some text below an image.
        </Text>
      </Surface>
    );
  },

  getImageHeight: function () {
    return Math.round(window.innerHeight / 2);
  },

  getImageStyle: function () {
    return {
      top: 0,
      left: 0,
      width: window.innerWidth,
      height: this.getImageHeight()
    };
  },

  getTextStyle: function () {
    return {
      top: this.getImageHeight() + 10,
      left: 0,
      width: window.innerWidth,
      height: 20,
      lineHeight: 20,
      fontSize: 12
    };
  }

});

ListView

Many mobile interfaces involve an infinitely long scrolling list of items. React Canvas provides the ListView component to do just that.

Because ListView virtualizes elements outside of the viewport, passing children to it is different than a normal React component where children are declared in render().

The numberOfItemsGetter, itemHeightGetter and itemGetter props are all required.

var ListView = ReactCanvas.ListView;

var MyScrollingListView = React.createClass({

  render: function () {
    return (
      <ListView
        numberOfItemsGetter={this.getNumberOfItems}
        itemHeightGetter={this.getItemHeight}
        itemGetter={this.renderItem} />
    );
  },

  getNumberOfItems: function () {
    // Return the total number of items in the list
  },

  getItemHeight: function () {
    // Return the height of a single item
  },

  renderItem: function (index) {
    // Render the item at the given index, usually a <Group>
  },

});

See the timeline example for a more complete example.

Currently, ListView requires that each item is of the same height. Future versions will support variable height items.

Text sizing

React Canvas provides the measureText function for computing text metrics.

The Page component in the timeline example contains an example of using measureText to achieve precise multi-line ellipsized text.

Custom fonts are not currently supported but will be added in a future version.

css-layout

There is experimental support for using css-layout to style React Canvas components. This is a more expressive way of defining styles for a component using standard CSS styles and flexbox.

Future versions may not support css-layout out of the box. The performance implications need to be investigated before baking this in as a core layout principle.

See the css-layout example.

Accessibility

This area needs further exploration. Using fallback content (the canvas DOM sub-tree) should allow screen readers such as VoiceOver to interact with the content. We've seen mixed results with the iOS devices we've tested. Additionally there is a standard for focus management that is not supported by browsers yet.

One approach that was raised by Bespin in 2009 is to keep a parallel DOM in sync with the elements rendered in canvas.

Running the examples

npm install
npm start

This will start a live reloading server on port 8080. To override the default server and live reload ports, run npm start with PORT and/or RELOAD_PORT environment variables.

A note on NODE_ENV and React: running the examples with NODE_ENV=production will noticeably improve scrolling performance. This is because React skips propType validation in production mode.

Using with webpack

The brfs transform is required in order to use the project with webpack.

npm install -g brfs
npm install --save-dev transform-loader brfs

Then add the brfs transform to your webpack config

module: {
  postLoaders: [
    { loader: "transform?brfs" }
  ]
}

Contributing

We welcome pull requests for bug fixes, new features, and improvements to React Canvas. Contributors to the main repository must accept Flipboard's Apache-style Individual Contributor License Agreement (CLA) before any changes can be merged.

Flipboard/react-canvas

{
"props": {
"initialPayload": {
"allShortcutsEnabled": false,
"path": "/",
"repo": {
"id": 30384844,
"defaultBranch": "master",
"name": "react-canvas",
"ownerLogin": "Flipboard",
"currentUserCanPush": false,
"isFork": false,
"isEmpty": false,
"createdAt": "2015-02-05T23:46:30.000Z",
"ownerAvatar": "https://avatars.githubusercontent.com/u/457049?v=4",
"public": true,
"private": false,
"isOrgOwned": true
},
"currentUser": null,
"refInfo": {
"name": "master",
"listCacheKey": "v0:1452296062.0",
"canEdit": false,
"refType": "branch",
"currentOid": "0b71180b4061a55410efb4578df2b65c1bf00a8e"
},
"tree": {
"items": [
{
"name": "examples",
"path": "examples",
"contentType": "directory"
},
{
"name": "lib",
"path": "lib",
"contentType": "directory"
},
{
"name": ".babelrc",
"path": ".babelrc",
"contentType": "file"
},
{
"name": ".gitignore",
"path": ".gitignore",
"contentType": "file"
},
{
"name": "LICENSE",
"path": "LICENSE",
"contentType": "file"
},
{
"name": "README.md",
"path": "README.md",
"contentType": "file"
},
{
"name": "gulpfile.js",
"path": "gulpfile.js",
"contentType": "file"
},
{
"name": "package.json",
"path": "package.json",
"contentType": "file"
},
{
"name": "webpack.config.js",
"path": "webpack.config.js",
"contentType": "file"
}
],
"templateDirectorySuggestionUrl": null,
"readme": null,
"totalCount": 9,
"showBranchInfobar": false
},
"fileTree": null,
"fileTreeProcessingTime": null,
"foldersToFetch": [],
"treeExpanded": false,
"symbolsExpanded": false,
"isOverview": true,
"overview": {
"banners": {
"shouldRecommendReadme": false,
"isPersonalRepo": false,
"showUseActionBanner": false,
"actionSlug": null,
"actionId": null,
"showProtectBranchBanner": false,
"publishBannersInfo": {
"dismissActionNoticePath": "/settings/dismiss-notice/publish_action_from_repo",
"releasePath": "/Flipboard/react-canvas/releases/new?marketplace=true",
"showPublishActionBanner": false
},
"interactionLimitBanner": null,
"showInvitationBanner": false,
"inviterName": null
},
"codeButton": {
"contactPath": "/contact",
"isEnterprise": false,
"local": {
"protocolInfo": {
"httpAvailable": true,
"sshAvailable": null,
"httpUrl": "https://github.com/Flipboard/react-canvas.git",
"showCloneWarning": null,
"sshUrl": null,
"sshCertificatesRequired": null,
"sshCertificatesAvailable": null,
"ghCliUrl": "gh repo clone Flipboard/react-canvas",
"defaultProtocol": "http",
"newSshKeyUrl": "/settings/ssh/new",
"setProtocolPath": "/users/set_protocol"
},
"platformInfo": {
"cloneUrl": "https://desktop.github.com",
"showVisualStudioCloneButton": false,
"visualStudioCloneUrl": "https://windows.github.com",
"showXcodeCloneButton": false,
"xcodeCloneUrl": "https://developer.apple.com",
"zipballUrl": "/Flipboard/react-canvas/archive/refs/heads/master.zip"
}
},
"newCodespacePath": "/codespaces/new?hide_repo_select=true&repo=30384844"
},
"popovers": {
"rename": null,
"renamedParentRepo": null
},
"commitCount": "84",
"overviewFiles": [
{
"displayName": "README.md",
"repoName": "react-canvas",
"refName": "master",
"path": "README.md",
"preferredFileType": "readme",
"tabName": "README",
"richText": "<article class=\"markdown-body entry-content container-lg\" itemprop=\"text\"><div class=\"markdown-heading\" dir=\"auto\"><h1 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">react-canvas</h1><a id=\"user-content-react-canvas\" class=\"anchor\" aria-label=\"Permalink: react-canvas\" href=\"#react-canvas\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<p dir=\"auto\"><a href=\"http://engineering.flipboard.com/2015/02/mobile-web\" rel=\"nofollow\">Introductory blog post</a></p>\n<p dir=\"auto\">React Canvas adds the ability for React components to render to <code>&lt;canvas&gt;</code> rather than DOM.</p>\n<p dir=\"auto\">This project is a work-in-progress. Though much of the code is in production on flipboard.com, the React canvas bindings are relatively new and the API is subject to change.</p>\n<div class=\"markdown-heading\" dir=\"auto\"><h2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">Motivation</h2><a id=\"user-content-motivation\" class=\"anchor\" aria-label=\"Permalink: Motivation\" href=\"#motivation\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<p dir=\"auto\">Having a long history of building interfaces geared toward mobile devices, we found that the reason mobile web apps feel slow when compared to native apps is the DOM. CSS animations and transitions are the fastest path to smooth animations on the web, but they have several limitations. React Canvas leverages the fact that most modern mobile browsers now have hardware accelerated canvas.</p>\n<p dir=\"auto\">While there have been other attempts to bind canvas drawing APIs to React, they are more focused on visualizations and games. Where React Canvas differs is in the focus on building application user interfaces. The fact that it renders to canvas is an implementation detail.</p>\n<p dir=\"auto\">React Canvas brings some of the APIs web developers are familiar with and blends them with a high performance drawing engine.</p>\n<div class=\"markdown-heading\" dir=\"auto\"><h2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">Installation</h2><a id=\"user-content-installation\" class=\"anchor\" aria-label=\"Permalink: Installation\" href=\"#installation\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<p dir=\"auto\">React Canvas is available through npm:</p>\n<p dir=\"auto\"><code>npm install react-canvas</code></p>\n<div class=\"markdown-heading\" dir=\"auto\"><h2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">React Canvas Components</h2><a id=\"user-content-react-canvas-components\" class=\"anchor\" aria-label=\"Permalink: React Canvas Components\" href=\"#react-canvas-components\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<p dir=\"auto\">React Canvas provides a set of standard React components that abstract the underlying rendering implementation.</p>\n<div class=\"markdown-heading\" dir=\"auto\"><h3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">&lt;Surface&gt;</h3><a id=\"user-content-surface\" class=\"anchor\" aria-label=\"Permalink: &lt;Surface&gt;\" href=\"#surface\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<p dir=\"auto\"><strong>Surface</strong> is the top-level component. Think of it as a drawing canvas in which you can place other components.</p>\n<div class=\"markdown-heading\" dir=\"auto\"><h3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">&lt;Layer&gt;</h3><a id=\"user-content-layer\" class=\"anchor\" aria-label=\"Permalink: &lt;Layer&gt;\" href=\"#layer\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<p dir=\"auto\"><strong>Layer</strong> is the the base component by which other components build upon. Common styles and properties such as top, width, left, height, backgroundColor and zIndex are expressed at this level.</p>\n<div class=\"markdown-heading\" dir=\"auto\"><h3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">&lt;Group&gt;</h3><a id=\"user-content-group\" class=\"anchor\" aria-label=\"Permalink: &lt;Group&gt;\" href=\"#group\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<p dir=\"auto\"><strong>Group</strong> is a container component. Because React enforces that all components return a single component in <code>render()</code>, Groups can be useful for parenting a set of child components. The Group is also an important component for optimizing scrolling performance, as it allows the rendering engine to cache expensive drawing operations.</p>\n<div class=\"markdown-heading\" dir=\"auto\"><h3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">&lt;Text&gt;</h3><a id=\"user-content-text\" class=\"anchor\" aria-label=\"Permalink: &lt;Text&gt;\" href=\"#text\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<p dir=\"auto\"><strong>Text</strong> is a flexible component that supports multi-line truncation, something which has historically been difficult and very expensive to do in DOM.</p>\n<div class=\"markdown-heading\" dir=\"auto\"><h3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">&lt;Image&gt;</h3><a id=\"user-content-image\" class=\"anchor\" aria-label=\"Permalink: &lt;Image&gt;\" href=\"#image\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<p dir=\"auto\"><strong>Image</strong> is exactly what you think it is. However, it adds the ability to hide an image until it is fully loaded and optionally fade it in on load.</p>\n<div class=\"markdown-heading\" dir=\"auto\"><h3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">&lt;Gradient&gt;</h3><a id=\"user-content-gradient\" class=\"anchor\" aria-label=\"Permalink: &lt;Gradient&gt;\" href=\"#gradient\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<p dir=\"auto\"><strong>Gradient</strong> can be used to set the background of a group or surface.</p>\n<div class=\"highlight highlight-source-js notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\" render() {\n ...\n return (\n &lt;Group style={this.getStyle()}&gt;\n &lt;Gradient style={this.getGradientStyle()} \n colorStops={this.getGradientColors()} /&gt;\n &lt;/Group&gt;\n );\n }\n getGradientColors(){\n return [\n { color: &quot;transparent&quot;, position: 0 },\n { color: &quot;#000&quot;, position: 1 }\n ]\n }\"><pre> <span class=\"pl-en\">render</span><span class=\"pl-kos\">(</span><span class=\"pl-kos\">)</span> <span class=\"pl-kos\">{</span>\n ...\n <span class=\"pl-en\">return</span> <span class=\"pl-kos\">(</span>\n <span class=\"pl-c1\">&lt;</span><span class=\"pl-ent\">Group</span> <span class=\"pl-c1\">style</span><span class=\"pl-c1\">=</span><span class=\"pl-kos\">{</span><span class=\"pl-smi\">this</span><span class=\"pl-kos\">.</span><span class=\"pl-en\">getStyle</span><span class=\"pl-kos\">(</span><span class=\"pl-kos\">)</span><span class=\"pl-kos\">}</span><span class=\"pl-c1\">&gt;</span>\n <span class=\"pl-c1\">&lt;</span><span class=\"pl-ent\">Gradient</span> <span class=\"pl-c1\">style</span><span class=\"pl-c1\">=</span><span class=\"pl-kos\">{</span><span class=\"pl-smi\">this</span><span class=\"pl-kos\">.</span><span class=\"pl-en\">getGradientStyle</span><span class=\"pl-kos\">(</span><span class=\"pl-kos\">)</span><span class=\"pl-kos\">}</span> \n <span class=\"pl-c1\">colorStops</span><span class=\"pl-c1\">=</span><span class=\"pl-kos\">{</span><span class=\"pl-smi\">this</span><span class=\"pl-kos\">.</span><span class=\"pl-en\">getGradientColors</span><span class=\"pl-kos\">(</span><span class=\"pl-kos\">)</span><span class=\"pl-kos\">}</span> <span class=\"pl-c1\">/</span><span class=\"pl-c1\">&gt;</span>\n <span class=\"pl-c1\">&lt;</span><span class=\"pl-c1\">/</span><span class=\"pl-ent\">Group</span><span class=\"pl-c1\">&gt;</span>\n <span class=\"pl-kos\">)</span><span class=\"pl-kos\">;</span>\n <span class=\"pl-kos\">}</span>\n <span class=\"pl-en\">getGradientColors</span><span class=\"pl-kos\">(</span><span class=\"pl-kos\">)</span><span class=\"pl-kos\"></span><span class=\"pl-kos\">{</span>\n <span class=\"pl-k\">return</span> <span class=\"pl-kos\">[</span>\n <span class=\"pl-kos\">{</span> <span class=\"pl-c1\">color</span>: <span class=\"pl-s\">\"transparent\"</span><span class=\"pl-kos\">,</span> <span class=\"pl-c1\">position</span>: <span class=\"pl-c1\">0</span> <span class=\"pl-kos\">}</span><span class=\"pl-kos\">,</span>\n <span class=\"pl-kos\">{</span> <span class=\"pl-c1\">color</span>: <span class=\"pl-s\">\"#000\"</span><span class=\"pl-kos\">,</span> <span class=\"pl-c1\">position</span>: <span class=\"pl-c1\">1</span> <span class=\"pl-kos\">}</span>\n <span class=\"pl-kos\">]</span>\n <span class=\"pl-kos\">}</span></pre></div>\n<div class=\"markdown-heading\" dir=\"auto\"><h3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">&lt;ListView&gt;</h3><a id=\"user-content-listview\" class=\"anchor\" aria-label=\"Permalink: &lt;ListView&gt;\" href=\"#listview\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<p dir=\"auto\"><strong>ListView</strong> is a touch scrolling container that renders a list of elements in a column. Think of it like UITableView for the web. It leverages many of the same optimizations that make table views on iOS and list views on Android fast.</p>\n<div class=\"markdown-heading\" dir=\"auto\"><h2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">Events</h2><a id=\"user-content-events\" class=\"anchor\" aria-label=\"Permalink: Events\" href=\"#events\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<p dir=\"auto\">React Canvas components support the same event model as normal React components. However, not all event types are currently supported.</p>\n<p dir=\"auto\">For a full list of supported events see <a href=\"/Flipboard/react-canvas/blob/master/lib/EventTypes.js\">EventTypes</a>.</p>\n<div class=\"markdown-heading\" dir=\"auto\"><h2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">Building Components</h2><a id=\"user-content-building-components\" class=\"anchor\" aria-label=\"Permalink: Building Components\" href=\"#building-components\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<p dir=\"auto\">Here is a very simple component that renders text below an image:</p>\n<div class=\"highlight highlight-source-js notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"var React = require('react');\nvar ReactCanvas = require('react-canvas');\n\nvar Surface = ReactCanvas.Surface;\nvar Image = ReactCanvas.Image;\nvar Text = ReactCanvas.Text;\n\nvar MyComponent = React.createClass({\n\n render: function () {\n var surfaceWidth = window.innerWidth;\n var surfaceHeight = window.innerHeight;\n var imageStyle = this.getImageStyle();\n var textStyle = this.getTextStyle();\n\n return (\n &lt;Surface width={surfaceWidth} height={surfaceHeight} left={0} top={0}&gt;\n &lt;Image style={imageStyle} src='...' /&gt;\n &lt;Text style={textStyle}&gt;\n Here is some text below an image.\n &lt;/Text&gt;\n &lt;/Surface&gt;\n );\n },\n\n getImageHeight: function () {\n return Math.round(window.innerHeight / 2);\n },\n\n getImageStyle: function () {\n return {\n top: 0,\n left: 0,\n width: window.innerWidth,\n height: this.getImageHeight()\n };\n },\n\n getTextStyle: function () {\n return {\n top: this.getImageHeight() + 10,\n left: 0,\n width: window.innerWidth,\n height: 20,\n lineHeight: 20,\n fontSize: 12\n };\n }\n\n});\"><pre><span class=\"pl-k\">var</span> <span class=\"pl-v\">React</span> <span class=\"pl-c1\">=</span> <span class=\"pl-en\">require</span><span class=\"pl-kos\">(</span><span class=\"pl-s\">'react'</span><span class=\"pl-kos\">)</span><span class=\"pl-kos\">;</span>\n<span class=\"pl-k\">var</span> <span class=\"pl-v\">ReactCanvas</span> <span class=\"pl-c1\">=</span> <span class=\"pl-en\">require</span><span class=\"pl-kos\">(</span><span class=\"pl-s\">'react-canvas'</span><span class=\"pl-kos\">)</span><span class=\"pl-kos\">;</span>\n\n<span class=\"pl-k\">var</span> <span class=\"pl-v\">Surface</span> <span class=\"pl-c1\">=</span> <span class=\"pl-v\">ReactCanvas</span><span class=\"pl-kos\">.</span><span class=\"pl-c1\">Surface</span><span class=\"pl-kos\">;</span>\n<span class=\"pl-k\">var</span> <span class=\"pl-v\">Image</span> <span class=\"pl-c1\">=</span> <span class=\"pl-v\">ReactCanvas</span><span class=\"pl-kos\">.</span><span class=\"pl-c1\">Image</span><span class=\"pl-kos\">;</span>\n<span class=\"pl-k\">var</span> <span class=\"pl-v\">Text</span> <span class=\"pl-c1\">=</span> <span class=\"pl-v\">ReactCanvas</span><span class=\"pl-kos\">.</span><span class=\"pl-c1\">Text</span><span class=\"pl-kos\">;</span>\n\n<span class=\"pl-k\">var</span> <span class=\"pl-v\">MyComponent</span> <span class=\"pl-c1\">=</span> <span class=\"pl-v\">React</span><span class=\"pl-kos\">.</span><span class=\"pl-en\">createClass</span><span class=\"pl-kos\">(</span><span class=\"pl-kos\">{</span>\n\n <span class=\"pl-en\">render</span>: <span class=\"pl-k\">function</span> <span class=\"pl-kos\">(</span><span class=\"pl-kos\">)</span> <span class=\"pl-kos\">{</span>\n <span class=\"pl-k\">var</span> <span class=\"pl-s1\">surfaceWidth</span> <span class=\"pl-c1\">=</span> <span class=\"pl-smi\">window</span><span class=\"pl-kos\">.</span><span class=\"pl-c1\">innerWidth</span><span class=\"pl-kos\">;</span>\n <span class=\"pl-k\">var</span> <span class=\"pl-s1\">surfaceHeight</span> <span class=\"pl-c1\">=</span> <span class=\"pl-smi\">window</span><span class=\"pl-kos\">.</span><span class=\"pl-c1\">innerHeight</span><span class=\"pl-kos\">;</span>\n <span class=\"pl-k\">var</span> <span class=\"pl-s1\">imageStyle</span> <span class=\"pl-c1\">=</span> <span class=\"pl-smi\">this</span><span class=\"pl-kos\">.</span><span class=\"pl-en\">getImageStyle</span><span class=\"pl-kos\">(</span><span class=\"pl-kos\">)</span><span class=\"pl-kos\">;</span>\n <span class=\"pl-k\">var</span> <span class=\"pl-s1\">textStyle</span> <span class=\"pl-c1\">=</span> <span class=\"pl-smi\">this</span><span class=\"pl-kos\">.</span><span class=\"pl-en\">getTextStyle</span><span class=\"pl-kos\">(</span><span class=\"pl-kos\">)</span><span class=\"pl-kos\">;</span>\n\n <span class=\"pl-k\">return</span> <span class=\"pl-kos\">(</span>\n <span class=\"pl-c1\">&lt;</span><span class=\"pl-v\">Surface</span> <span class=\"pl-c1\">width</span><span class=\"pl-c1\">=</span><span class=\"pl-kos\">{</span><span class=\"pl-s1\">surfaceWidth</span><span class=\"pl-kos\">}</span> <span class=\"pl-c1\">height</span><span class=\"pl-c1\">=</span><span class=\"pl-kos\">{</span><span class=\"pl-s1\">surfaceHeight</span><span class=\"pl-kos\">}</span> <span class=\"pl-c1\">left</span><span class=\"pl-c1\">=</span><span class=\"pl-kos\">{</span><span class=\"pl-c1\">0</span><span class=\"pl-kos\">}</span> <span class=\"pl-c1\">top</span><span class=\"pl-c1\">=</span><span class=\"pl-kos\">{</span><span class=\"pl-c1\">0</span><span class=\"pl-kos\">}</span><span class=\"pl-c1\">&gt;</span>\n <span class=\"pl-c1\">&lt;</span><span class=\"pl-v\">Image</span> <span class=\"pl-c1\">style</span><span class=\"pl-c1\">=</span><span class=\"pl-kos\">{</span><span class=\"pl-s1\">imageStyle</span><span class=\"pl-kos\">}</span> <span class=\"pl-c1\">src</span><span class=\"pl-c1\">=</span><span class=\"pl-s\">'...'</span> <span class=\"pl-c1\">/</span><span class=\"pl-c1\">&gt;</span>\n <span class=\"pl-c1\">&lt;</span><span class=\"pl-v\">Text</span> <span class=\"pl-c1\">style</span><span class=\"pl-c1\">=</span><span class=\"pl-kos\">{</span><span class=\"pl-s1\">textStyle</span><span class=\"pl-kos\">}</span><span class=\"pl-c1\">&gt;</span>\n Here is some text below an image.\n <span class=\"pl-c1\">&lt;</span><span class=\"pl-c1\">/</span><span class=\"pl-v\">Text</span><span class=\"pl-c1\">&gt;</span>\n <span class=\"pl-c1\">&lt;</span><span class=\"pl-c1\">/</span><span class=\"pl-v\">Surface</span><span class=\"pl-c1\">&gt;</span>\n <span class=\"pl-kos\">)</span><span class=\"pl-kos\">;</span>\n <span class=\"pl-kos\">}</span><span class=\"pl-kos\">,</span>\n\n <span class=\"pl-en\">getImageHeight</span>: <span class=\"pl-k\">function</span> <span class=\"pl-kos\">(</span><span class=\"pl-kos\">)</span> <span class=\"pl-kos\">{</span>\n <span class=\"pl-k\">return</span> <span class=\"pl-v\">Math</span><span class=\"pl-kos\">.</span><span class=\"pl-en\">round</span><span class=\"pl-kos\">(</span><span class=\"pl-smi\">window</span><span class=\"pl-kos\">.</span><span class=\"pl-c1\">innerHeight</span> <span class=\"pl-c1\">/</span> <span class=\"pl-c1\">2</span><span class=\"pl-kos\">)</span><span class=\"pl-kos\">;</span>\n <span class=\"pl-kos\">}</span><span class=\"pl-kos\">,</span>\n\n <span class=\"pl-en\">getImageStyle</span>: <span class=\"pl-k\">function</span> <span class=\"pl-kos\">(</span><span class=\"pl-kos\">)</span> <span class=\"pl-kos\">{</span>\n <span class=\"pl-k\">return</span> <span class=\"pl-kos\">{</span>\n <span class=\"pl-c1\">top</span>: <span class=\"pl-c1\">0</span><span class=\"pl-kos\">,</span>\n <span class=\"pl-c1\">left</span>: <span class=\"pl-c1\">0</span><span class=\"pl-kos\">,</span>\n <span class=\"pl-c1\">width</span>: <span class=\"pl-smi\">window</span><span class=\"pl-kos\">.</span><span class=\"pl-c1\">innerWidth</span><span class=\"pl-kos\">,</span>\n <span class=\"pl-c1\">height</span>: <span class=\"pl-smi\">this</span><span class=\"pl-kos\">.</span><span class=\"pl-en\">getImageHeight</span><span class=\"pl-kos\">(</span><span class=\"pl-kos\">)</span>\n <span class=\"pl-kos\">}</span><span class=\"pl-kos\">;</span>\n <span class=\"pl-kos\">}</span><span class=\"pl-kos\">,</span>\n\n <span class=\"pl-en\">getTextStyle</span>: <span class=\"pl-k\">function</span> <span class=\"pl-kos\">(</span><span class=\"pl-kos\">)</span> <span class=\"pl-kos\">{</span>\n <span class=\"pl-k\">return</span> <span class=\"pl-kos\">{</span>\n <span class=\"pl-c1\">top</span>: <span class=\"pl-smi\">this</span><span class=\"pl-kos\">.</span><span class=\"pl-en\">getImageHeight</span><span class=\"pl-kos\">(</span><span class=\"pl-kos\">)</span> <span class=\"pl-c1\">+</span> <span class=\"pl-c1\">10</span><span class=\"pl-kos\">,</span>\n <span class=\"pl-c1\">left</span>: <span class=\"pl-c1\">0</span><span class=\"pl-kos\">,</span>\n <span class=\"pl-c1\">width</span>: <span class=\"pl-smi\">window</span><span class=\"pl-kos\">.</span><span class=\"pl-c1\">innerWidth</span><span class=\"pl-kos\">,</span>\n <span class=\"pl-c1\">height</span>: <span class=\"pl-c1\">20</span><span class=\"pl-kos\">,</span>\n <span class=\"pl-c1\">lineHeight</span>: <span class=\"pl-c1\">20</span><span class=\"pl-kos\">,</span>\n <span class=\"pl-c1\">fontSize</span>: <span class=\"pl-c1\">12</span>\n <span class=\"pl-kos\">}</span><span class=\"pl-kos\">;</span>\n <span class=\"pl-kos\">}</span>\n\n<span class=\"pl-kos\">}</span><span class=\"pl-kos\">)</span><span class=\"pl-kos\">;</span></pre></div>\n<div class=\"markdown-heading\" dir=\"auto\"><h2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">ListView</h2><a id=\"user-content-listview-1\" class=\"anchor\" aria-label=\"Permalink: ListView\" href=\"#listview-1\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<p dir=\"auto\">Many mobile interfaces involve an infinitely long scrolling list of items. React Canvas provides the ListView component to do just that.</p>\n<p dir=\"auto\">Because ListView virtualizes elements outside of the viewport, passing children to it is different than a normal React component where children are declared in render().</p>\n<p dir=\"auto\">The <code>numberOfItemsGetter</code>, <code>itemHeightGetter</code> and <code>itemGetter</code> props are all required.</p>\n<div class=\"highlight highlight-source-js notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"var ListView = ReactCanvas.ListView;\n\nvar MyScrollingListView = React.createClass({\n\n render: function () {\n return (\n &lt;ListView\n numberOfItemsGetter={this.getNumberOfItems}\n itemHeightGetter={this.getItemHeight}\n itemGetter={this.renderItem} /&gt;\n );\n },\n\n getNumberOfItems: function () {\n // Return the total number of items in the list\n },\n\n getItemHeight: function () {\n // Return the height of a single item\n },\n\n renderItem: function (index) {\n // Render the item at the given index, usually a &lt;Group&gt;\n },\n\n});\"><pre><span class=\"pl-k\">var</span> <span class=\"pl-v\">ListView</span> <span class=\"pl-c1\">=</span> <span class=\"pl-v\">ReactCanvas</span><span class=\"pl-kos\">.</span><span class=\"pl-c1\">ListView</span><span class=\"pl-kos\">;</span>\n\n<span class=\"pl-k\">var</span> <span class=\"pl-v\">MyScrollingListView</span> <span class=\"pl-c1\">=</span> <span class=\"pl-v\">React</span><span class=\"pl-kos\">.</span><span class=\"pl-en\">createClass</span><span class=\"pl-kos\">(</span><span class=\"pl-kos\">{</span>\n\n <span class=\"pl-en\">render</span>: <span class=\"pl-k\">function</span> <span class=\"pl-kos\">(</span><span class=\"pl-kos\">)</span> <span class=\"pl-kos\">{</span>\n <span class=\"pl-k\">return</span> <span class=\"pl-kos\">(</span>\n <span class=\"pl-c1\">&lt;</span><span class=\"pl-v\">ListView</span>\n <span class=\"pl-c1\">numberOfItemsGetter</span><span class=\"pl-c1\">=</span><span class=\"pl-kos\">{</span><span class=\"pl-smi\">this</span><span class=\"pl-kos\">.</span><span class=\"pl-c1\">getNumberOfItems</span><span class=\"pl-kos\">}</span>\n <span class=\"pl-c1\">itemHeightGetter</span><span class=\"pl-c1\">=</span><span class=\"pl-kos\">{</span><span class=\"pl-smi\">this</span><span class=\"pl-kos\">.</span><span class=\"pl-c1\">getItemHeight</span><span class=\"pl-kos\">}</span>\n <span class=\"pl-c1\">itemGetter</span><span class=\"pl-c1\">=</span><span class=\"pl-kos\">{</span><span class=\"pl-smi\">this</span><span class=\"pl-kos\">.</span><span class=\"pl-c1\">renderItem</span><span class=\"pl-kos\">}</span> <span class=\"pl-c1\">/</span><span class=\"pl-c1\">&gt;</span>\n <span class=\"pl-kos\">)</span><span class=\"pl-kos\">;</span>\n <span class=\"pl-kos\">}</span><span class=\"pl-kos\">,</span>\n\n <span class=\"pl-en\">getNumberOfItems</span>: <span class=\"pl-k\">function</span> <span class=\"pl-kos\">(</span><span class=\"pl-kos\">)</span> <span class=\"pl-kos\">{</span>\n <span class=\"pl-c\">// Return the total number of items in the list</span>\n <span class=\"pl-kos\">}</span><span class=\"pl-kos\">,</span>\n\n <span class=\"pl-en\">getItemHeight</span>: <span class=\"pl-k\">function</span> <span class=\"pl-kos\">(</span><span class=\"pl-kos\">)</span> <span class=\"pl-kos\">{</span>\n <span class=\"pl-c\">// Return the height of a single item</span>\n <span class=\"pl-kos\">}</span><span class=\"pl-kos\">,</span>\n\n <span class=\"pl-en\">renderItem</span>: <span class=\"pl-k\">function</span> <span class=\"pl-kos\">(</span><span class=\"pl-s1\">index</span><span class=\"pl-kos\">)</span> <span class=\"pl-kos\">{</span>\n <span class=\"pl-c\">// Render the item at the given index, usually a &lt;Group&gt;</span>\n <span class=\"pl-kos\">}</span><span class=\"pl-kos\">,</span>\n\n<span class=\"pl-kos\">}</span><span class=\"pl-kos\">)</span><span class=\"pl-kos\">;</span></pre></div>\n<p dir=\"auto\">See the <a href=\"/Flipboard/react-canvas/blob/master/examples/timeline/app.js\">timeline example</a> for a more complete example.</p>\n<p dir=\"auto\">Currently, ListView requires that each item is of the same height. Future versions will support variable height items.</p>\n<div class=\"markdown-heading\" dir=\"auto\"><h2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">Text sizing</h2><a id=\"user-content-text-sizing\" class=\"anchor\" aria-label=\"Permalink: Text sizing\" href=\"#text-sizing\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<p dir=\"auto\">React Canvas provides the <code>measureText</code> function for computing text metrics.</p>\n<p dir=\"auto\">The <a href=\"/Flipboard/react-canvas/blob/master/examples/timeline/components/Page.js\">Page component</a> in the timeline example contains an example of using measureText to achieve precise multi-line ellipsized text.</p>\n<p dir=\"auto\">Custom fonts are not currently supported but will be added in a future version.</p>\n<div class=\"markdown-heading\" dir=\"auto\"><h2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">css-layout</h2><a id=\"user-content-css-layout\" class=\"anchor\" aria-label=\"Permalink: css-layout\" href=\"#css-layout\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<p dir=\"auto\">There is experimental support for using <a href=\"https://github.com/facebook/css-layout\">css-layout</a> to style React Canvas components. This is a more expressive way of defining styles for a component using standard CSS styles and flexbox.</p>\n<p dir=\"auto\">Future versions may not support css-layout out of the box. The performance implications need to be investigated before baking this in as a core layout principle.</p>\n<p dir=\"auto\">See the <a href=\"/Flipboard/react-canvas/blob/master/examples/css-layout\">css-layout example</a>.</p>\n<div class=\"markdown-heading\" dir=\"auto\"><h2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">Accessibility</h2><a id=\"user-content-accessibility\" class=\"anchor\" aria-label=\"Permalink: Accessibility\" href=\"#accessibility\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<p dir=\"auto\">This area needs further exploration. Using fallback content (the canvas DOM sub-tree) should allow screen readers such as VoiceOver to interact with the content. We've seen mixed results with the iOS devices we've tested. Additionally there is a standard for <a href=\"http://www.w3.org/TR/2010/WD-2dcontext-20100304/#dom-context-2d-drawfocusring\" rel=\"nofollow\">focus management</a> that is not supported by browsers yet.</p>\n<p dir=\"auto\">One approach that was raised by <a href=\"http://vimeo.com/3195079\" rel=\"nofollow\">Bespin</a> in 2009 is to keep a <a href=\"http://robertnyman.com/2009/04/03/mozilla-labs-online-code-editor-bespin/#comment-560310\" rel=\"nofollow\">parallel DOM</a> in sync with the elements rendered in canvas.</p>\n<div class=\"markdown-heading\" dir=\"auto\"><h2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">Running the examples</h2><a id=\"user-content-running-the-examples\" class=\"anchor\" aria-label=\"Permalink: Running the examples\" href=\"#running-the-examples\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<div class=\"snippet-clipboard-content notranslate position-relative overflow-auto\" data-snippet-clipboard-copy-content=\"npm install\nnpm start\"><pre class=\"notranslate\"><code>npm install\nnpm start\n</code></pre></div>\n<p dir=\"auto\">This will start a live reloading server on port 8080. To override the default server and live reload ports, run <code>npm start</code> with PORT and/or RELOAD_PORT environment variables.</p>\n<p dir=\"auto\"><strong>A note on NODE_ENV and React</strong>: running the examples with <code>NODE_ENV=production</code> will noticeably improve scrolling performance. This is because React skips propType validation in production mode.</p>\n<div class=\"markdown-heading\" dir=\"auto\"><h2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">Using with webpack</h2><a id=\"user-content-using-with-webpack\" class=\"anchor\" aria-label=\"Permalink: Using with webpack\" href=\"#using-with-webpack\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<p dir=\"auto\">The <a href=\"https://github.com/substack/brfs\">brfs</a> transform is required in order to use the project with webpack.</p>\n<div class=\"highlight highlight-source-shell notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"npm install -g brfs\nnpm install --save-dev transform-loader brfs\"><pre>npm install -g brfs\nnpm install --save-dev transform-loader brfs</pre></div>\n<p dir=\"auto\">Then add the <a href=\"https://github.com/substack/brfs\">brfs</a> transform to your webpack config</p>\n<div class=\"highlight highlight-source-js notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"module: {\n postLoaders: [\n { loader: &quot;transform?brfs&quot; }\n ]\n}\"><pre>module: <span class=\"pl-kos\">{</span>\n <span class=\"pl-c1\">postLoaders</span>: <span class=\"pl-kos\">[</span>\n <span class=\"pl-kos\">{</span> <span class=\"pl-c1\">loader</span>: <span class=\"pl-s\">\"transform?brfs\"</span> <span class=\"pl-kos\">}</span>\n <span class=\"pl-kos\">]</span>\n<span class=\"pl-kos\">}</span></pre></div>\n<div class=\"markdown-heading\" dir=\"auto\"><h2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\">Contributing</h2><a id=\"user-content-contributing\" class=\"anchor\" aria-label=\"Permalink: Contributing\" href=\"#contributing\"><svg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"></path></svg></a></div>\n<p dir=\"auto\">We welcome pull requests for bug fixes, new features, and improvements to React Canvas. Contributors to the main repository must accept Flipboard's Apache-style <a href=\"https://docs.google.com/forms/d/1gh9y6_i8xFn6pA15PqFeye19VqasuI9-bGp_e0owy74/viewform\" rel=\"nofollow\">Individual Contributor License Agreement (CLA)</a> before any changes can be merged.</p>\n</article>",
"loaded": true,
"timedOut": false,
"errorMessage": null,
"headerInfo": {
"toc": [
{
"level": 1,
"text": "react-canvas",
"anchor": "react-canvas",
"htmlText": "react-canvas"
},
{
"level": 2,
"text": "Motivation",
"anchor": "motivation",
"htmlText": "Motivation"
},
{
"level": 2,
"text": "Installation",
"anchor": "installation",
"htmlText": "Installation"
},
{
"level": 2,
"text": "React Canvas Components",
"anchor": "react-canvas-components",
"htmlText": "React Canvas Components"
},
{
"level": 3,
"text": "<Surface>",
"anchor": "surface",
"htmlText": "&lt;Surface&gt;"
},
{
"level": 3,
"text": "<Layer>",
"anchor": "layer",
"htmlText": "&lt;Layer&gt;"
},
{
"level": 3,
"text": "<Group>",
"anchor": "group",
"htmlText": "&lt;Group&gt;"
},
{
"level": 3,
"text": "<Text>",
"anchor": "text",
"htmlText": "&lt;Text&gt;"
},
{
"level": 3,
"text": "<Image>",
"anchor": "image",
"htmlText": "&lt;Image&gt;"
},
{
"level": 3,
"text": "<Gradient>",
"anchor": "gradient",
"htmlText": "&lt;Gradient&gt;"
},
{
"level": 3,
"text": "<ListView>",
"anchor": "listview",
"htmlText": "&lt;ListView&gt;"
},
{
"level": 2,
"text": "Events",
"anchor": "events",
"htmlText": "Events"
},
{
"level": 2,
"text": "Building Components",
"anchor": "building-components",
"htmlText": "Building Components"
},
{
"level": 2,
"text": "ListView",
"anchor": "listview-1",
"htmlText": "ListView"
},
{
"level": 2,
"text": "Text sizing",
"anchor": "text-sizing",
"htmlText": "Text sizing"
},
{
"level": 2,
"text": "css-layout",
"anchor": "css-layout",
"htmlText": "css-layout"
},
{
"level": 2,
"text": "Accessibility",
"anchor": "accessibility",
"htmlText": "Accessibility"
},
{
"level": 2,
"text": "Running the examples",
"anchor": "running-the-examples",
"htmlText": "Running the examples"
},
{
"level": 2,
"text": "Using with webpack",
"anchor": "using-with-webpack",
"htmlText": "Using with webpack"
},
{
"level": 2,
"text": "Contributing",
"anchor": "contributing",
"htmlText": "Contributing"
}
],
"siteNavLoginPath": "/login?return_to=https%3A%2F%2Fgithub.com%2FFlipboard%2Freact-canvas"
}
},
{
"displayName": "LICENSE",
"repoName": "react-canvas",
"refName": "master",
"path": "LICENSE",
"preferredFileType": "license",
"tabName": "BSD-3-Clause",
"richText": null,
"loaded": false,
"timedOut": false,
"errorMessage": null,
"headerInfo": {
"toc": null,
"siteNavLoginPath": "/login?return_to=https%3A%2F%2Fgithub.com%2FFlipboard%2Freact-canvas"
}
}
],
"overviewFilesProcessingTime": 16.53877
}
},
"appPayload": {
"helpUrl": "https://docs.github.com",
"findFileWorkerPath": "/assets-cdn/worker/find-file-worker-a007d7f370d6.js",
"findInFileWorkerPath": "/assets-cdn/worker/find-in-file-worker-d0f0ff069004.js",
"githubDevUrl": null,
"enabled_features": {
"code_nav_ui_events": false,
"copilot_conversational_ux": false,
"react_blob_overlay": false,
"copilot_conversational_ux_embedding_update": false,
"copilot_popover_file_editor_header": true,
"copilot_smell_icebreaker_ux": true,
"copilot_workspace": false
}
}
}
}
{
"accept-ranges": "bytes",
"cache-control": "max-age=0, private, must-revalidate",
"content-encoding": "gzip",
"content-security-policy": "default-src 'none'; base-uri 'self'; child-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com api.githubcopilot.com objects-origin.githubusercontent.com *.actions.githubusercontent.com wss://*.actions.githubusercontent.com productionresultssa0.blob.core.windows.net/ productionresultssa1.blob.core.windows.net/ productionresultssa2.blob.core.windows.net/ productionresultssa3.blob.core.windows.net/ productionresultssa4.blob.core.windows.net/ productionresultssa5.blob.core.windows.net/ productionresultssa6.blob.core.windows.net/ productionresultssa7.blob.core.windows.net/ productionresultssa8.blob.core.windows.net/ productionresultssa9.blob.core.windows.net/ productionresultssa10.blob.core.windows.net/ productionresultssa11.blob.core.windows.net/ productionresultssa12.blob.core.windows.net/ productionresultssa13.blob.core.windows.net/ productionresultssa14.blob.core.windows.net/ productionresultssa15.blob.core.windows.net/ productionresultssa16.blob.core.windows.net/ productionresultssa17.blob.core.windows.net/ productionresultssa18.blob.core.windows.net/ productionresultssa19.blob.core.windows.net/ github-production-repository-image-32fea6.s3.amazonaws.com github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com wss://alive.github.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com copilot-workspace.githubnext.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src 'self' data: github.githubassets.com media.githubusercontent.com camo.githubusercontent.com identicons.github.com avatars.githubusercontent.com github-cloud.s3.amazonaws.com objects.githubusercontent.com secured-user-images.githubusercontent.com/ user-images.githubusercontent.com/ private-user-images.githubusercontent.com opengraph.githubassets.com github-production-user-asset-6210df.s3.amazonaws.com customer-stories-feed.github.com spotlights-feed.github.com objects-origin.githubusercontent.com *.githubusercontent.com; manifest-src 'self'; media-src github.com user-images.githubusercontent.com/ secured-user-images.githubusercontent.com/ private-user-images.githubusercontent.com github-production-user-asset-6210df.s3.amazonaws.com gist.github.com; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; upgrade-insecure-requests; worker-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/",
"content-type": "text/html; charset=utf-8",
"date": "Mon, 22 Apr 2024 09:40:33 GMT",
"etag": "80555d3628a9bae2c343bf7fd91a41fb",
"referrer-policy": "no-referrer-when-downgrade",
"server": "GitHub.com",
"set-cookie": "logged_in=no; Path=/; Domain=github.com; Expires=Tue, 22 Apr 2025 09:40:32 GMT; HttpOnly; Secure; SameSite=Lax",
"strict-transport-security": "max-age=31536000; includeSubdomains; preload",
"transfer-encoding": "chunked",
"vary": "X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, Accept-Encoding, Accept, X-Requested-With",
"x-content-type-options": "nosniff",
"x-frame-options": "deny",
"x-github-request-id": "B40E:214787:D1E1:11AAF:66263090",
"x-xss-protection": "0"
}