Skip to content

Commit 0bfeaac

Browse files
author
Dima Voytenko
committed
A unified placeholder and fallback system for layout.
1 parent 5c51e91 commit 0bfeaac

File tree

14 files changed

+547
-132
lines changed

14 files changed

+547
-132
lines changed

builtins/amp-video.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {assertHttpsUrl} from '../src/url';
1919
import {getLengthNumeral, isLayoutSizeDefined} from '../src/layout';
2020
import {loadPromise} from '../src/event-helper';
2121
import {registerElement} from '../src/custom-element';
22+
import {setStyles} from '../src/style';
2223

2324

2425
/**
@@ -36,10 +37,14 @@ export function installVideo(win) {
3637

3738
/** @override */
3839
layoutCallback() {
39-
// TODO(dvoytenko): Add re-layout as well.
40-
var width = this.element.getAttribute('width');
41-
var height = this.element.getAttribute('height');
42-
var video = document.createElement('video');
40+
let width = this.element.getAttribute('width');
41+
let height = this.element.getAttribute('height');
42+
let video = document.createElement('video');
43+
if (!video.play) {
44+
this.toggleFallback(true);
45+
return Promise.resolve();
46+
}
47+
4348
if (this.element.getAttribute('src')) {
4449
assertHttpsUrl(this.element.getAttribute('src'), this.element);
4550
}
@@ -56,9 +61,13 @@ export function installVideo(win) {
5661
video.appendChild(child);
5762
});
5863
this.element.appendChild(video);
64+
5965
/** @private {?HTMLVideoElement} */
6066
this.video_ = video;
61-
return loadPromise(video);
67+
setStyles(video, {visibility: 'hidden'});
68+
return loadPromise(video).then(() => {
69+
setStyles(video, {visibility: ''});
70+
});
6271
}
6372

6473
/** @override */

css/amp.css

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,15 +130,20 @@ i-amp-sizer {
130130
/* Just state. */
131131
}
132132

133-
[placeholder] {
133+
.-amp-element > [placeholder] {
134134
display: block;
135135
}
136136

137-
[placeholder].hidden {
137+
.-amp-element > [placeholder].hidden {
138138
visibility: hidden;
139139
}
140140

141-
.-amp-layout-size-defined [placeholder] {
141+
.-amp-element:not(.amp-notsupported) > [fallback] {
142+
display: none;
143+
}
144+
145+
.-amp-layout-size-defined > [placeholder],
146+
.-amp-layout-size-defined > [fallback] {
142147
position: absolute !important;
143148
top: 0 !important;
144149
left: 0 !important;
@@ -188,6 +193,11 @@ amp-pixel {
188193
visibility: hidden;
189194
}
190195

196+
amp-video video {
197+
margin: 0 !important;
198+
padding: 0 !important;
199+
}
200+
191201
.-amp-loader {
192202
position: absolute;
193203
display: block;

extensions/amp-anim/0.1/amp-anim.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,15 @@ class AmpAnim extends AMP.BaseElement {
3333

3434
/** @override */
3535
buildCallback() {
36-
/** @private @const {?Element} */
37-
this.placeholder_ = this.getPlaceholder();
38-
3936
/** @private @const {!Element} */
4037
this.img_ = new Image();
4138
this.propagateAttributes(['alt'], this.img_);
4239
this.applyFillContent(this.img_);
4340
this.img_.width = getLengthNumeral(this.element.getAttribute('width'));
4441
this.img_.height = getLengthNumeral(this.element.getAttribute('height'));
4542

46-
// The image shown/hidden depends on placeholder.
47-
st.toggle(this.img_, !this.placeholder_);
43+
// The image is initially hidden if a placeholder is available.
44+
st.toggle(this.img_, !this.getPlaceholder());
4845

4946
this.element.appendChild(this.img_);
5047

@@ -66,14 +63,17 @@ class AmpAnim extends AMP.BaseElement {
6663
return this.updateImageSrc_();
6764
}
6865

66+
/** @override */
67+
firstLayoutCompleted() {
68+
// Keep the placeholder: amp-anim is using it to start/stop playing.
69+
}
70+
6971
/** @override */
7072
viewportCallback(inViewport) {
71-
if (this.placeholder_) {
72-
if (!inViewport || !this.loadPromise_) {
73-
this.updateInViewport_();
74-
} else {
75-
this.loadPromise_.then(() => this.updateInViewport_());
76-
}
73+
if (!inViewport || !this.loadPromise_) {
74+
this.updateInViewport_();
75+
} else {
76+
this.loadPromise_.then(() => this.updateInViewport_());
7777
}
7878
}
7979

@@ -87,7 +87,7 @@ class AmpAnim extends AMP.BaseElement {
8787
/** @private */
8888
updateInViewport_() {
8989
let inViewport = this.isInViewport();
90-
this.placeholder_.classList.toggle('hidden', inViewport);
90+
this.togglePlaceholder(!inViewport);
9191
st.toggle(this.img_, inViewport);
9292
}
9393

extensions/amp-carousel/0.1/test/test-carousel.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ describe('Carousel gestures', () => {
2626
element = document.createElement('div');
2727
element.style.width = '320px';
2828
element.style.height = '200px';
29+
element.getRealChildren = () => [];
2930
document.body.appendChild(element);
3031

3132
carousel = new AmpCarousel(element);

extensions/amp-carousel/0.1/test/test-slides.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ describe('Slides functional', () => {
4242
slide0.classList.add('slide0');
4343
slide1.classList.add('slide1');
4444
slide2.classList.add('slide2');
45+
element.getRealChildren = () => [slide0, slide1, slide2];
4546
return element;
4647
}
4748

spec/amp-html-format.md

Lines changed: 6 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -246,42 +246,15 @@ Built-in components are always available in an AMP document and have a dedicated
246246

247247
### Common attributes
248248

249-
#### `width`, `height`
249+
#### `layout`, `width`, `height`, `media`, `placeholder`, `fallback`
250250

251-
Depending on the value of the `layout` attribute AMP component elements must have a `width` and `height` attribute that contains an integer pixel value. Actual layout behavior is determined by the `layout` attribute.
251+
These attributes define the layout of an element. The key goal here is to ensure that
252+
the element can be displayed and its space can be properly reserved before any of the
253+
JavaScript or remote resources have been downloaded.
252254

253-
#### `layout`
255+
See the [AMP Layout System](./amp-html-layout.md) for details about the layout system.
254256

255-
The optional layout attribute allows specifying how the component behaves in the document layout. Valid values for the layout attribute are:
256-
257-
- Not present: If `width` equals to `auto` `fixed-height` layout is assumed. If `width` or `height` attributes are present `fixed` layout is assumed. If `width` and `height` are not present `container` layout is assumed (unless otherwise documented with the component) which may not be supported by the element (Would trigger a runtime error).
258-
- `fixed`: The `width` and `height` attributes must be present. The only exceptions are `amp-pixel` and `amp-audio` elements.
259-
- `fixed-height`: The `height` attribute must be present. The `width` attribute must not be present or must be equal to `auto`.
260-
- `responsive`: The `width` and `height` attributes must be present and are used to determine the aspect ratio of the component and the component is sized to the width of its container element while maintaining the height based on the aspect ratio.
261-
- `nodisplay`: The component takes up zero space on the screen as if its display style was `none`. The `width` and `height` attributes are not required.
262-
- `fill`: Element size will be determined by the parent element.
263-
- `container`: The component is assumed to not have specific layout itself but only act as a container. Its children as rendered immediately.
264-
265-
#### `media`
266-
267-
All AMP custom elements support the `media` attribute. The value of media is a media query. If the query does not match, the element is not rendered at all and it's resources and potentially it's child resources will not be fetched. If the browser window changes size or orientation the media queries are re-evaluated and elements are hidden and shown based on the new results.
268-
269-
Example: Here we have 2 images with mutually exclusive media queries. Depending on the screen width one or the other will be fetched and rendered. Note that the media attribute is available on all custom elements, so it can be used with non-image elements such as ads.
270-
271-
```html
272-
<amp-img
273-
media="(min-width: 650px)"
274-
src="wide.jpg"
275-
width=466
276-
height=355 layout="responsive" ></amp-img>
277-
<amp-img
278-
media="(max-width: 649px)"
279-
src="narrow.jpg"
280-
width=527
281-
height=193 layout="responsive" ></amp-img>
282-
```
283-
284-
### `on`
257+
#### `on`
285258

286259
The `on` attribute is used to install event handlers on elements. The events that are supported depend on the element.
287260

spec/amp-html-layout.md

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<!---
2+
Copyright 2015 The AMP HTML Authors. All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS-IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-->
16+
17+
# AMP HTML ⚡ Layout System
18+
19+
## Overview
20+
21+
The main goal of the layout system is to ensure that AMP elements can express their layout
22+
so that the runtime is able to infer sizing of elements before any remote resources, such as
23+
JavaScript and data calls, have been completed. This is important since this significantly
24+
reduces rendering and scrolling jank.
25+
26+
With this in mind the AMP Layout System is designed to support few but flexible layout
27+
that provide good performance guarantees. This system relies on a set of attributes such
28+
as `layout`, `width` and `height` to express the element's layout and sizing needs.
29+
30+
## Layout Attributes
31+
32+
### `width` and `height`
33+
34+
Depending on the value of the `layout` attribute AMP component elements must have a `width` and
35+
`height` attribute that contains an integer pixel value. Actual layout behavior is determined by the
36+
`layout` attribute as described below.
37+
38+
In a few cases if `width` or `height` are not specified the AMP runtime can default these values
39+
as following:
40+
- `amp-pixel`: Both `width` and `height` are defaulted to 0.
41+
- `amp-audio`: The default `width` and `height` are inferred from browser.
42+
43+
### `layout`
44+
45+
The optional layout attribute allows specifying how the component behaves in the document layout.
46+
Valid values for the layout attribute are:
47+
48+
- Not present: The `layout` will be inferred as following:
49+
- if `width` equals to `auto` `fixed-height` layout is assumed;
50+
- if `width` or `height` attributes are present `fixed` layout is assumed;
51+
- if `width` and `height` are not present `container` layout is assumed
52+
- `fixed`: The `width` and `height` attributes must be present. The only exceptions are `amp-pixel`
53+
and `amp-audio` elements.
54+
- `fixed-height`: The `height` attribute must be present. The `width` attribute must not be present
55+
or must be equal to `auto`.
56+
- `responsive`: The `width` and `height` attributes must be present and are used to determine the
57+
aspect ratio of the component. The component is sized to the width of its container element while
58+
maintaining the height based on the aspect ratio.
59+
- `fill`: Element size will be determined by the parent element.
60+
- `container`: The component is assumed to not have specific layout itself but only act as a
61+
container. Its children are rendered immediately.
62+
- `nodisplay`: The component takes up zero space on the screen as if its display style was `none`.
63+
The `width` and `height` attributes are not required.
64+
65+
Each element documents which `layout` values it supported. If an element does not support the
66+
specified value it would trigger a runtime error.
67+
68+
### `media`
69+
70+
All AMP custom elements support the `media` attribute. The value of media is a media query. If the query does not match, the element is not rendered at all and it's resources and potentially it's child resources will not be fetched. If the browser window changes size or orientation the media queries are re-evaluated and elements are hidden and shown based on the new results.
71+
72+
Example: Here we have 2 images with mutually exclusive media queries. Depending on the screen width one or the other will be fetched and rendered. Note that the media attribute is available on all custom elements, so it can be used with non-image elements such as ads.
73+
74+
```html
75+
<amp-img
76+
media="(min-width: 650px)"
77+
src="wide.jpg"
78+
width=466
79+
height=355 layout="responsive" ></amp-img>
80+
<amp-img
81+
media="(max-width: 649px)"
82+
src="narrow.jpg"
83+
width=527
84+
height=193 layout="responsive" ></amp-img>
85+
```
86+
87+
### `placeholder`
88+
89+
The `placeholder` attribute can be set on any HTML element, not just AMP elements. It indicates that
90+
the element marked with this attribute acts as a placeholder for the parent AMP element. If specified
91+
a placeholder element must be a direct child of the AMP element. By default, the placeholder is
92+
immediately shown for the AMP element, even if the AMP element's resources have not been downloaded
93+
or initialized. Once ready the AMP element typically hides its placeholder and shows the content.
94+
The exact behavior w.r.t. to placeholder is up to the element's implementation.
95+
96+
```html
97+
<amp-anim src="animated.gif" width=466 height=355 layout="responsive" >
98+
<amp-img placeholder src="preview.png" layout="fill"></amp-img>
99+
</amp-anim>
100+
```
101+
102+
### `fallback`
103+
104+
The `fallback` attribute can be set on any HTML element, not just AMP elements. It's a convention that
105+
allows the element to communicate to the reader that the browser does not support it. If specified
106+
a fallback element must be a direct child of the AMP element. The exact behavior w.r.t. to fallback
107+
is up to the element's implementation.
108+
109+
```html
110+
<amp-anim src="animated.gif" width=466 height=355 layout="responsive" >
111+
<div fallback>Cannot play animated images on this device.</div>
112+
</amp-anim>
113+
```
114+
115+
## Behavior
116+
117+
A non-container (`layout != container`) AMP element starts up in the unresolved/unbuilt mode in which
118+
all of its children are hidden except for a placeholder (see `placeholder` attribute). The JavaScript
119+
and data payload necessary to fully construct the element may still be downloading and initializing,
120+
but the AMP runtime already knows how to size and layout the element only relying on CSS classes and
121+
`layout`, `width`, `height` and `media` attributes. In most cases a `placeholder`, if specified, is
122+
sized and positioned to take all of the element's space.
123+
124+
The `placeholder` is hidden as soon as the element is built and its first layout complete. At this
125+
point the element is expected to have all of its children properly built and positioned and ready
126+
to be displayed and accept a reader's input. This is the default behavior. Each element can override
127+
to, e.g., hide `placeholder` faster or keep it around longer.
128+
129+
The element is sized and displayed based on the `layout`, `width`, `height` and `media` attributes
130+
by the runtime. All of the layout rules are implemented via CSS internally. The element is said to
131+
"define size" if its size is inferrable via CSS styles and does not change based on its children:
132+
available immediately or inserted dynamically. This does not mean that this element's size cannot
133+
change. The layout could be fully responsive as is the case with `responsive`, `fixed-height` and
134+
`fill` layouts. It simply means that the size does not change without an explicit user action, e.g.
135+
during rendering or scrolling or post download.
136+
137+
If the element has been configured incorrectly it will not be rendered at all in PROD and in DEV mode
138+
the runtime will display the element in the error state. Possible errors include invalid or unsupported
139+
values of `layout`, `width` and `height` attributes.
140+
141+
## (tl;dr) Appendix 1: Layout Table
142+
143+
What follows is the table of layouts, acceptable configuration parameters and CSS classes and styles
144+
used by this layouts. Notice that:
145+
1. Any CSS class marked prefixed with "-amp-" and elements prefixed with "i-amp-" are considered to be
146+
internal to AMP and their use in user stylesheets is not allowed. They are shown here simply for
147+
informational purposes.
148+
2. The only layouts that currently do not "define size" are `container` and `nodisplay`.
149+
3. Even though `width` and `height` are specified in the table as required the default rules may
150+
apply as is the case with `amp-pixel` and `amp-audio`.
151+
152+
| Layout | Width/Height Required? | Defines Size? | Additional Elements | CSS "display" |
153+
|--------------|------------------------|---------------|---------------------|---------------|
154+
| nodisplay | no | no | no | `none` |
155+
| fixed | yes | yes, specified by `width` and `height` | no | `inline-block` |
156+
| responsive | yes | yes, based on parent container and aspect ratio of `width:height` | yes, `i-amp-sizer` | `block` |
157+
| fixed-height | `height` only. `width` can be `auto` | yes, specified by the parent container and `height` | no | `block` |
158+
| fill | no | yes, parent's size | no | `block` |
159+
| container | no | no | no | `block` |

0 commit comments

Comments
 (0)