Skip to content

Instantly share code, notes, and snippets.

@cathyxz
Last active July 31, 2019 20:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cathyxz/c015f137ed089405b95cb01b8fef5927 to your computer and use it in GitHub Desktop.
Save cathyxz/c015f137ed089405b95cb01b8fef5927 to your computer and use it in GitHub Desktop.
Dynamic Resizing

Dynamic Resizing in Rendered Components

cathyzhu@ | go/amp-list-resizing Draft: April 30, 2019

Overview

Rendered data in or can sometimes be dynamically sized depending on the XHR, or change size based on user interaction. We need to allow the list to dynamically resize for legitimate use cases without causing or enabling content-shifting. To do this, we introduce layout="CONTAINER" in and with certain caveats. Background We currently allow rendering remote data via the <amp-list> component whose behavior of <amp-list> regarding sizing is as follows:

We require amp-list to specify a height, fallback, and optionally a placeholder, and do not allow it to resize if the bottom of the list is in the viewport (we show an overflow button, which resizes the <amp-list> on user click) to prevent content-shifting.

This is problematic for three categories of use cases that are detailed in a Use Case section below. For example:

  • The number of elements in the <amp-list> is not known beforehand and can vary between 0 and filling up the entire page.
  • The <amp-list> may resize on user interaction, e.g. it contains resizable components like <amp-accordion> or has a grid vs. list view that updates on user action.
  • The number of items in <amp-list> changes as the result of the refresh action or changing a bound [src] attribute.

Proposed API

Pre-rendered Components

<amp-state id="static-foo" [src]="src-foo"> 
<script type="application/json">
  [
    {"name": "a"}, 
    {"name": "b"}, 
    {"name": "c"}
]
</script>
</amp-state>

<amp-list src="amp-state://static-foo" layout="container">
  <template type="amp-mustache">
    <span>{{name}}</span>
  </template>
</amp-list>

Since the data for this particular is included verbatim inside the AMP document itself, its load-time is guaranteed and not dependent on an XHR. Therefore we can allow it to be layout container and size itself based on its children. Subsequent resize attempts will all succeed. If a refresh action or a change on the bound src attribute results in triggering a new fetch, a loader will be overlayed on the existing elements until the fetch completes and replaces the old elements with the new results. The rendering component will maintain its old height until its new contents are ready. load-more will behave in a similar manner.

Components without Pre-rendering

For components that are not pre-rendered, layout container will behave slightly differently. It requires an initial width and height. Sample Code

<amp-list src="/endpoint" layout="container" height="300" width="400">
  <template type="amp-mustache">
    <span>{{name}}</span>
  </template>
</amp-list>

Expected Behavior

  • If the content is below the viewport, it can freely resize.
  • If the bottom of the content is below the viewport, it can freely expand.
  • If the loaded content is less than the allocated space AND the rendered component is within the viewport, allow the component to shrink to the size of its children.
  • Block rendering elements beneath this element for X seconds and resize it according to the height of its children IF the fetch completes. If not, cut off the rest of the component and allow it to maintain its initial size.
  • If a user action with trust-level HIGH results in a resize attempt, allow it to resize. Including the following actions:
    • src change or refresh
    • Show / hide / toggle
    • CSS class changes on the amp-list itself
    • load-more if applicable

Discussion Questions

  • Naming - should we still call this layout container or maybe layout resizable? Please bikeshed based on devX
    • Option 1: Layout CONTAINER has different behaviors depending on whether or not the component is prerendered / SSR-ed.
    • Option 2: Layout CONTAINER can only be applied to SSR/Prerendered components, we can apply an attribute resizable to all size-defined layouts exhibiting the above-defined behavior.
  • Is it ok to cut off the rest of the component, or should we introduce an overflow element and allow user action to trigger resize?
    • Option 1: strict cutoff, no overflow behavior.
      • Pro: Easy to understand, strict cutoff, developer has to fix.
      • Con: more complicated to implement, since it deviates from our standard overflow behavior. No recourse for cutoff content.
    • Option 2: provide overflow attribute.
      • Pro: user has recourse for seeing cutoff content.
      • Con: is generally a bad UX anyway in all use cases we’ve seen, developer may prefer to fix it. More complex behavior is more difficult for devs to reason with.
  • What restrictions should we place on width if any?

Use Case Breakdown

Dynamic Resizing on First Load

The Problem

TLDR: We don’t know how many items are going to be in the list before the network request returns, and therefore can’t size it properly at document generation time.

Amp-list renders items based on the contents of a network request, but we require the list to adhere to a height specified in markup before we know the contents of that network request.

We currently require a amp-list to specify a height, fallback, and optionally a placeholder, and do not allow it to resize if the bottom of the list is in the viewport (we show an overflow button) to prevent content-shifting.

endpoints frequently return a variable number of items, e.g. shopping cart items can return 1 item, or more-than-1-viewport number of items, and there is often content underneath the list that needs to be shown.

This results in a catch 22 situation where people either have an overflow button for a large number of items, or end up pushing the subsequent content outside the viewport leaving a large blank space in the middle of the viewport when there are few items.

Use Cases

  • Shopping Cart List with Checkout Form
    • overflow button truncating a checkout list is poor UX
    • having the payment form under the viewport after a large space is also bad since users will not realize there is a payment option without scrolling)
  • Product Search Page with infinite scroll (overflow button impacts infinite scroll, but search can also return no items).
    • Infinite scroll should never trigger overflow if there are more items to be shown, since overflow button basically kills the infinite scroll experience.
    • But search can also return no / few items, in which case it’s legit to put sections like “recommended items” below and not leave a viewport-sized gap.
    • Note that this can also be solved by allowing the list to shrink. There are legitimate situations where people do want to put content below the infinite scroll. (Or do we actually want to take a super hard stance on this and say no?)

Caveats

We don’t want to allow abuse of any of these solutions to show expanding ads.

Potential Solutions

Use layout container for static initial layout (i.e. Alan’s static rendering proposal in ampproject/amphtml#20752). Block rendering after the amp-list for X seconds. Allow resizing if rendered within X seconds, or show overflow if rendered after. Add some kind of filter to detect and disallow abuse by ads. Basically allow content shifting for all well-intentioned usages. Slightly relax the content-shifting constraint to allow the rendering component to shrink, but not to expand.

Solution

From a UX perspective, we can make the observation that content shifting is only bad when content the user is focusing on shifts. This means that we can: Always resize things below the viewport (as we already do) Shrink empty space within the viewport - although this may bring new content into the viewport from below, it won’t affect any content above the shrinking empty space the user may be focused on. Freely resize content if the visible content below it is not meaningful to the user. In practice, that means content that has not yet finished loading (either naturally or because we’ve blocked its loading).

Image credits to nainar@ and awatterson@.

Scenario A B C D E Grow ✔️(not controversial) ✔️(not controversial) ✔️ ❌ ❌ Shrink ✔️(not controversial) ✔️ ✔️ ❌ ❌

In the cases where we can't allow the amp-list to grow or shrink. We should instead do the following:

  • Block content after from rendering for some time (X seconds)
  • If amp-list gets a json result in that time:
  • Lay out the list and following content accordingly
  • If amp-list doesn't get a json result in that time:
  • Collapse the amp-list to the static minimum height provided by the developer
  • Layout the following content after the list

Dynamic Resizing on User Interaction

The Problem

Developers may decide to alter the number of items in the list, or to change their UI, which results in the height of the list changing.

Use Cases

If I change the src, height may need to change due to a different number of items being present in the list. If I do filtering, the list height may need to shrink. If I change the display mode from list to grid, the list height may need to change. If I click on an amp-accordion inside an amp-list, the list height may need to change.

Potential Solutions

  • Action + bindable attribute (current solution: changeToLayoutContainer, is-layout-container)
  • Auto-detect when to change to layout container (previously discussed in design review at ampproject/amphtml#18659 (comment)).
  • High trust event + intent to mutate amp-list should result in layout container
  • Allow layout container the way it works in the rest of AMP.

Dynamic Resizing on Re-fetch or Refresh

The Problem

<amp-list> and <amp-render> both offer the refresh action, which re-fetches data from the same src. It also allows the src attribute to be bound via <amp-bind>, which on change, triggers a new fetch to a new endpoint or sources data from a different <amp-state>. Both of these actions may cause the data being templated to change, and hence change the height of <amp-render> or <amp-list>. Use Cases Server-side filter and sort Refreshing stale data (e.g. a ticker, live tweets, sports scores, etc.) Solution In both cases, the height of the rendered component should be allowed to change, since both actions are High Trust actions that require explicit user intent.

Further Reading

go/amp-list-prd go/amp-render-design go/amp-list-design

Complex Use Cases

Product Search Page Filter / sort may change the height of the list Grid view vs. list view

Sometimes the filters themselves need to be dynamically generated AND are inside an accordion

Product Detail Page Product Details Need to live update inventory on color / size selection. Accordions may exist on the page

Resize per accordion (Source: Rent the Runway)

Product Reviews Need filter, sort, pagination.

Need simple math or object data along with list Tags can be dynamic number of rows

Shipping and Handling The province list depends on the selected country list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment