ampproject / amphtml

The AMP web component framework.
https://amp.dev
Apache License 2.0
14.89k stars 3.89k forks source link

I2I: amp-base-carousel #20595

Open sparhami opened 5 years ago

sparhami commented 5 years ago

Summary

Implement a new carousel, like <amp-carousel>, that addresses many of the feature requests for the existing <amp-carousel>.

Overview

We want to build 3 separate components, sharing an underlying implementation, to address user needs for a carousel:

Motivation

There are currently several feature requests for <amp-carousel> that are difficult to implement in the current version. Additionally, a new version will allow us to correct a few API pain points on the existing carousel. The new version of carousel aims to address several requested features, including:

See https://github.com/ampproject/amphtml/projects/80#column-4187313 for a list of issues / feature requests.

Design

Overview

The new version of carousel (currently version 0.2), unifies both <amp-carousel type="carousel"> and <amp-carousel type="slides">, along with all the new feature requests into a single implementation. Rather than using a type to configure the carousel, individual features can be configured. For example, you can configure the carousel to show one slide at a time (e.g. type slides) / use the existing dimensions of the slides (e.g. type carousel) separately from whether or not you want to snap on slides.

The API aims to be flexible, without making any assumptions on how developers plan to use it. For example, you can configure the carousel to show 3 slides at a time and snap on every slide, every second slide, or on every third slide, etc. This is in contrast with a grouping based configuration that would tie the number of visible slides to which ones can snap.

Layout and Responsiveness

One thing we would like to address is having components that can work well on multiple screen sizes by default. The API section below will cover how we can configure attributes to change value depending on screen size.

Note: This part is still not clear needs more discussion on what approach we should take at a high level. Other than configuration of the component attributes, we want to be able to have layout change depending on viewport. For example, we want to have the carousel to look like:

      desktop                     mobile
|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|       |‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|
|                  |       |                  |
|                  |       |                  |
|                  |       |      ......      |
|__________________|       |__________________|
[ t ][ t ][ t ][ t ]

Where t are thumbnails. In order to save space on mobile, they become dots. This causes the aspect ratio of the carousel itself to change. The size of the thumbnails may be responsive as well, but it may make more sense to have them be a fixed size (or respond to breakpoints in the page).

Another use case where our current layout model falls short is sizing of space for captions. We want the space for the captions be fixed (so we can render a certain number of lines of text), while the overall carousel is responsive. Consider the following diagram:

      large                    small
|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|       |‾‾‾‾‾‾‾‾‾‾‾‾‾‾|
|                |       |              |
|                |       |______________|
|________________|       | some captio… |
| some captions  |        ‾‾‾‾‾‾‾‾‾‾‾‾‾‾
 ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾

The aspect ratio of the image portion should stay the same, but the overall aspect ratio of the component changes. This can be accomplished using the heights attribute with layout="responsive". For example, if you wanted a 4:3 aspect ratio for the image, and 2 lines of text with 1.25 line-height, you would do:

<!-- width and height are not used -->
<amp-base-carousel layout="responsive" width="1" height="1"
    heights="calc(100% * 4/3 + 2.5em)">
</amp-base-carousel>

The heights attribute also allows you to use media queries. For example, if you want to display either 3 or 4 slides at a time, you can change the aspect ratio of the carousel as follows:

<!-- Using an aspect ratio of 4:3 for the slides in this example. -->
<amp-base-carousel
    layout="responsive" width="1" height="1"
    heights="(min-width: 600px) calc(100% * 4 * 4 / 3), calc(100% * 3 * 4 / 3)"
    visible-count="(min-width: 600px) 4, 3">
  …
</amp-base-carousel>

We might want to add an assert to the carousel that makes sure that you request space for a caption using such a mechanism.

API

This API covers the base carousel implementation.

Customizing Previous/Next Buttons

There are two aspects to the customization:

  1. Appearance
  2. Accessibility Label

In carousel 0.1, the appearance of the buttons was customized by exposing a class that developers could target. This had the unfortunate consequence of making it difficult for us to change the default styling, since developers were customizing their arrows in a way that assumed our existing CSS was present. The accessibility label was customized by specifying an attribute that would determine how to generate the label for the button.

Carousel 0.2 aims to address both of these by using an approach similar to <amp-image-slider>, where developers can instead pass the buttons as children if they want to customize them. This allows full customization of the arrows, without us worrying about making incompatible changes in the future. The API follows the slots API and looks like:

<amp-base-carousel ...>
  <amp-img src="..."></amp-img>
  <amp-img src="..."></amp-img>
  <button slot="next-arrow">Next</button>
  <button slot="prev-arrow">Prev</button>
</amp-base-carousel>

The carousel also exposes next and prev actions which can be hooked up to arbitrary buttons, for developers that want further customization.

<amp-base-carousel id="carousel-1" ...>
  <div slot="next-arrow"></div>
  <div slot="prev-arrow"></div>
</amp-base-carousel>
<button on="tap:carousel-1.prev()">Prev</button>
<button on="tap:carousel-1.next()">Next</button>

Snap

In contrast with carousel 0.1, the new version only uses CSS based snapping. This means that snapping will work in IE 10+/Edge, Firefox 39+, Chrome 69+ and Safari 11+. The carousel will simply not snap on older browsers.

There are three attributes that control the snapping behavior:

  1. snap = "true" or "false"
  2. snap-align = "center" or "start" Where to snap on slides. When the value is start, the starting edge of the carousel is aligned with a starting edge of a slide. When the value is center, the center of the carousel is aligned with the center of a slide. This also controls what is considered as the "center" index of the carousel, as well as how a slide will be aligned for the goToSlide action. Note that this behavior applies, even when snap is false. It may make sense to rename this align, slide-align or some other name.
  3. snap-by = Number Which slides to snap on. For example, snap-align="2" means that the carousel will snap on every other slide. This is primarily useful when used with the visible-count attribute.

Horizontal and Vertical Layout

The carousel may be configured to lay out either horizontally or vertically. This is controlled by the horizontal attribute, either "true" or "false", defaulting to "true". All functionality for the carousel is supported in both layout directions.

Grouping / Number of Visible Slides

Slides may be grouped to allow the carousel to show multiple slides at a time. This functionality exists in the carousel API for a few reasons:

  1. To allow the the group size to change depending on a media query. Otherwise, it would be sufficient to simply put multiple items within a single slide.
  2. Allow snapping / advancing to move a fewer number of slides than the whole group.
  3. To allow for better behavior when the number of slides is not divisible by the group size. There is some additional logic to handle the ending edge.

There are three attributes that affect the behavior:

  1. mixed-length = "true" or "false" When this is set, the carousel will use the existing length (either width or height, depending on carousel orientation) of the slides.
  2. visible-count = Number This determines how many slide are visible at a time. This is ignored when mixed-length is set to "true". This is mostly equivalent to giving each slide a length of calc(100% / visible-count) in the mixed-length case, though there are a couple situations where it will do additional things.

    This may be a floating point value. For example: visible-count="1.2" will show 1 whole slide, with 20% of an additional slide showing. This allows for "peeking" of additional slides as an affordance to the user indicating the carousel is swipeable.

  3. advance-count = Number How many slides to move forward (or backwards) when triggering the next or prev actions.

Auto Advance

In contrast with carousel 0.1, auto advance does not enable looping, though the carousel will still go back to the first slide when auto advancing from the last slide. Auto advance will be paused on user interaction.

There are three attributes that affect the behavior:

  1. auto-advance = "true" or "false"
  2. auto-advance-interval = Number How often to wait between automatic advances.
  3. auto-advance-count = Number How many slides to advance when doing an auto advance. This is primarily useful with the visible-count attribute. A negative number may be used to advance in the opposite direction. This is useful for RTL languages, where the expected advancement direction would be to the left.

Loop

This functionality is controlled by the loop attribute, either "true" or "false"

Initial Index

This functionality is controlled by the initial-index attribute, which is a Number

Responsive Configuration and Binding of Attributes

All of the above attributes in the carousel will be both bindable and controllable via media queries. The media query syntax is a comma separated list of media query and value pairs. This follows the syntax for the sizes attribute and looks like follows:

<amp-base-carousel visible-count="(min-width: 600px) 4, (min-width: 400px) 3, 2"></amp-base-carousel>
<amp-base-carousel visible-count="3"></amp-base-carousel>
<amp-base-carousel [visible-count]="some.state.value"></amp-base-carousel>

The value for first matching media query (read left to right) is used. The final group does not have an associated media query and is used if no other media queries match. In the first example, if the width is 600px or above, the visible-count is 4, 400px to 599px it is 3, and 2 if the width is 399px or less.

The responsive configuration using media queries allows for many configurations of the carousel without needing to duplicate slides between multiple carousels. It also allows for keeping scroll position when changing configuration. For example, if the visible count changes when changing orientation, the currently showing slide (as defined by the alignment) will be displayed after the change.

Note that if you change the visible-count, you may want to change the aspect ratio of the carousel itself to match the aspect ratio of the slides. This can currently be done via CSS. It may make sense to allow similar responsive configuration of layout attributes, but that is outside of the scope of the carousel itself.

Handling Slide Changes

The new version of the carousel will support changing the slides (e.g. filtering some out or swapping them out entirely), though this is not a part of the MVP. This can be done by composing the carousel with an another component that can render multiple items or using amp-script. For example, consider the hypothetical <amp-renderer> component, that replaces a <template> with the rendered items:

<amp-renderer [src]="...">
  <amp-base-carousel ...>
    <template>
      <amp-img src="{{src}}" ...></amp-img>
    </template>
  </amp-base-carousel>
</amp-renderer>

Note that the direct children of the carousel are the slides, rather than the <amp-renderer> component. This is necessary to support slotting using Shadow DOM.

Passing in Captions

The <amp-inline-gallery> component is planned to support captions for slides. Currently, <amp-lightbox-gallery> supports captions, using the format:

<figure>
  <amp-img ...></amp-img>
  <figcaption>caption</figcaption>
</figure>

It also supports using aria-describedby to reference a caption by id. This structure is not ideal for the carousel however, as we want to support updates to the DOM (e.g. initiated from amp-script) being reflected in the rendered UI. We could potentially use MutationObserver to do this, but there are limitations. We cannot change the original (passed-in) DOM as that would cause problems for some UI diffing based approaches. We could clone nodes over as they change and put them in the correct spot, but some things like event listeners cannot be cloned.

We can create a helper component that will allow us to layout the slides with captions correctly. The usage would look like:

<amp-inline-gallery...>
  <amp-slide>
    <div>First slide</div>
    <div slot="caption">First slide's caption</div>
  </amp-slide>
  <div>Second slide</div>
  <amp-slide>
    <div slot="caption">Third slide's caption</div>
    <div>Third slide</div>
  </amp-slide>
</amp-inline-gallery>

The <amp-slide> component can then wrap the caption into a component that can do JS text truncation and show an overflow button (e.g. see more, which will open the image into a lightbox with an expanded caption). The component can also put the slide content/caption into a <figure>/<figcaption>.

User Scrollability

The API for this is TBD. The carousel may be configured to ignore user-scrolling (e.g. via touch), but still be advanced using next/prev buttons. This may be useful if the carousel exists within a page that is itself swipeable.

Implementation

Scrollable Container Layout

The carousel places all slides in the scrolling container. All of the slides are present in the DOM and not hidden. The runtime code will determine which slides to lay out depending on which ones are visible within the carousel.

In looping mode, the carousel reserves enough space on either side of the current item to move all the way to the next slide (in the backwards direction) and the previous slide (in the forwards direction). This allows the user to quickly move from one end of the carousel to the other, without passing where they started. This is done using "spacer" elements that reserve enough space on both ends of the carousel. For example, for a carousel with 3 slides, with the current index as slide 1, this looks like:

[ ][3][1][2][ ]

the two empty brackets indicate placeholders where slides will be moved. For example, if the user moves left, slide '2' is moved into the empty spot. The user will not be able to swipe past this point in a single gesture (or a series of rapid gestures) as there is no more room on the left to scroll. Once the user has stopped scrolling for a bit, they will be able to scroll further left, back to slide '1'.

As the user scrolls, slides are moved around using transform: translate(x, y) into the correct position. There is an open question on how many slides to move at a given time. Moving more allows for more buffer during rapid scrolling, as well as better preloading of slides. On the other hand, it increases the number of GPU layers created, which may be a problem if a carousel has a large number of slides.

Responsive Attributes

When an attribute value changes or the component is first instantiated, the value is parsed into pairs of media queries and values. The media queries are then turned into MediaQueryLists. The onchange is used to check when a media query changes, and the resulting attribute value is checked to see if it has changed.

When an attribute value changes (e.g. via <amp-bind>), all existing MediaQueryLists for the attribute have the onchange removed. New MediaQueryLists are generated and listened to. The media queries are re-evaluated to find whether or not there is a new value.

Open questions

See Also

https://github.com/ampproject/amphtml/issues/5981

/cc @ampproject/wg-ui

sparhami commented 5 years ago

Created #20851 for allowing additional height to layout="responsive".

lethnh commented 5 years ago

why i tried used to amp-base-carousel not working for me ? can you help me ?

sparhami commented 5 years ago

why i tried used to amp-base-carousel not working for me ? can you help me ?

amp-base-carousel is still experimental, but you can try it out by toggling the amp-base-carousel experiment. This can be done using the browser console, running AMP.toggleExperiment('amp-base-carousel', true);. This needs to be done once per domain that you want test on. There are currently a few issues with it, but you can try it out if you want. We are also working on two other components, amp-inline-gallery and amp-stream-gallery which we hope to have ready soon that we think most developers will want to use instead of amp-base-carousel directly.

lethnh commented 5 years ago

@sparhami you can tell me when feature amp-base-carousel releasing I want to create a slide has 6 elements per row and this slide scroll vertical and I tried used to amp-carousel but in local. I want deploy slider in server you can suggest me to do slider Can you help me ?

sparhami commented 5 years ago

@sparhami you can tell me when feature amp-base-carousel releasing I want to create a slide has 6 elements per row and this slide scroll vertical and I tried used to amp-carousel but in local. I want deploy slider in server you can suggest me to do slider Can you help me ?

If you want to do vertical slides, you will want to use amp-base-carousel, but we still need to do a bit more work on it. I don't have a firm timeline, but we are currently working on putting out amp-stream-gallery (#24388) and will shortly follow up with amp-inline-gallery. Once those are both out, we will loop back and finish up and release amp-base-carousel.

stale[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions.