Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Master feature] Make stories respect responsive units#15955

Closed
newmuis opened this issue Jun 11, 2018 · 15 comments
Closed

[Master feature] Make stories respect responsive units #15955

newmuis opened this issue Jun 11, 2018 · 15 comments

Comments

@newmuis
Copy link
Contributor

newmuis commented Jun 11, 2018

The fact that the amp-story desktop UI shows up to three amp-story-page elements in view at once can be problematic for designing responsively, because the size of the amp-story-page differs from the size of the viewport, so common responsive practices that are based on the viewport dimensions won't work (e.g. media queries, and the viewport units vw/vh/vmin/vmax).

Screenshot of three panels displayed side-by-side

We can resolve this by rewriting these styles at runtime to refer to the size of the amp-story-page rather than the viewport, but this becomes problematic once there are multiple possible amp-story elements in the document (as will be possible if/when the standalone attribute becomes optional). As such, it becomes important to have scoping for a story's styles, so that we can determine which sizing to use for a given selector.

Proposal

Design

tl;dr: The design is to achieve style isolation by having publishers create <style> tags that are scoped to the <amp-story> that they affect and enforcing the isolation by using JavaScript to modify the selectors at runtime.

Validation changes

  • If standalone attribute is missing on amp-story, id is required

Runtime changes

  • In amp-story buildCallback:
    • Apply an attribute identifying the story to each amp-story-page, per the page initialization section
    • Modify the <style> tag that matches this story's id, per the style modifications section
  • At ad injection time
    • Assume ads must be standalone
    • Apply an attribute identifying the sponsored story (using an auto-generated id like i-amphtml-story-id-ad-1) to each amp-story-page, per the page initialization section
    • Modify the <style amp-custom> tag from the ad fetch per the style modifications section

Page initialization

  • When a page is initialized, we should apply an attribute to the page that identifies the story from which it originates, in the format of .i-amphtml-story-id-${id}. For example, given the following <amp-story> tag:
<amp-story id="foo">
  <amp-story-page id="p1"></amp-story-page>
  <amp-story-page id="p2"></amp-story-page>
  <amp-story-page id="p3"></amp-story-page>
</amp-story>

We would transform this to:

<amp-story id="foo">
  <amp-story-page id="p1" class="i-amphtml-story-id-foo"></amp-story-page>
  <amp-story-page id="p2" class="i-amphtml-story-id-foo"></amp-story-page>
  <amp-story-page id="p3" class="i-amphtml-story-id-foo"></amp-story-page>
</amp-story>

Style modifications

Preface each selector with one that matches the class format specified in the page initialization section above (.i-amphtml-story-id-${id}).
For example, given the following <style> tag:

<style amp-story="foo">
  div {
    background-color: yellow;
  }
</style>

We would transform this to:

<style amp-story="foo">
  .i-amphtml-story-id-foo div {
    background-color: yellow;
  }
</style>
  • Replace all vw/vh/vmin/vmax with the px values based on the size of the amp-story-page element
    • Optionally, update on resize
    • Use pixel values instead of percentage, because we don't know how deeply-nested the target of the rule is
    • There is already some code to convert viewport units in the AMP runtime
  • TODO(newmuis): Somehow, make media queries compare against the size of the amp-story-page element instead of the viewport

Examples

Example 1

Publisher-authored DOM:

<html >
  <head>
    <style amp-custom>
      p {
        background: red;
      }
    </style>
  </head>
  <body>
    <amp-story standalone>
      <amp-story-page id="p1">
        <p id="hello-world">
          Hello world!
        </p>
      </amp-story-page>
    </amp-story>
  </body>
</html>

DOM after amp-story build:

<html >
  <head>
    <style amp-custom amp-story="standalone">
      .i-amphtml-story-id-standalone p {
        background: red;
      }
    </style>
  </head>
  <body>
    <amp-story standalone>
      <amp-story-page id="p1" class="i-amphtml-story-id-standalone">
        <p id="hello-world">
          Hello world!
        </p>
      </amp-story-page>
    </amp-story>
  </body>
</html>

Notes:

Because of the presence of the standalone attribute on the amp-story tag, all amp-custom styles for this document are assumed to be for the story present in the document. Because the id was absent in the publisher DOM, it is auto-generated. The id is applied to both the story and the styles.

Result:

  • p#hello-world will have a red background.

Example 2

Publisher-authored DOM:

<html >
  <head>
    <style amp-custom>
      p {
        background: green;
      }
    </style>
    <style amp-story="s1">
      p {
        background: orange;
      }
    </style>
    <style amp-story="s2">
      p {
        background: blue;
      }
    </style>
  </head>
  <body>
    <p id="intro">
      Lorem ipsum...
    </p>
    <amp-story id="s1">
      <amp-story-page id="s1p1">
        <p id="hello-world">
          Hello world!
        </p>
      </amp-story-page>
    </amp-story>
    <p id="middle">
      Etiam a tempor...
    </p>
    <amp-story id="s2">
      <amp-story-page id="s2p1">
        <p id="goodbye-world">
          Goodbye world!
        </p>
      </amp-story-page>
    </amp-story>
  </body>
</html>

DOM after amp-story build:

<html >
  <head>
    <style amp-custom>
      p {
        background: green;
      }
    </style>
    <style amp-story="s1">
      .i-amphtml-story-id-s1 p {
        background: orange;
      }
    </style>
    <style amp-story="s2">
      .i-amphtml-story-id-s2 p {
        background: blue;
      }
    </style>
  </head>
  <body>
    <p id="intro">
      Lorem ipsum...
    </p>
    <amp-story id="s1">
      <amp-story-page id="s1p1" class="i-amphtml-story-id-s1">
        <p>Hello world!</p>
      </amp-story-page>
    </amp-story>
    <p id="middle">
      Etiam a tempor...
    </p>
    <amp-story id="s2">
      <amp-story-page id="s2p1" class="i-amphtml-story-id-s2">
        <p>Goodbye world!</p>
      </amp-story-page>
    </amp-story>
  </body>
</html>

Notes:

Because there is no standalone attribute on the amp-story tag, an id is specified on each of the stories. An accompanying <style> tag is found and these styles will be used for each story respectively.

Result:

  • p#intro will have a green background.
  • p#hello-world will have an orange background.
  • p#middle will have a green background.
  • p#goodbye-world will have a blue background.

Alternatives considered

Wrapping the contents of each amp-story-page in an iframe

Pros

  • Provides style isolation
  • Provides JS isolation
  • Allows analytics to work by just creating a new analytics config at runtime and injecting it into the frame

Cons

  • Causes serious problems due to unexpectedly reparenting other AMP components
  • Requires injecting amp-custom styles, amp-runtime styles, and amp-extension="*" styles into every frame
  • Requires amp-story extension to block the initialization of other AMP extensions, which goes against AMP principles
    Potentially very expensive and unlikely to work on lower-end devices

Wrapping the contents of each amp-story-page in a shadow root

Pros

  • Provides style isolation

Cons

  • Messes up styles that are reliant on the host context (e.g. amp-story h1), since the styles would need to be rewritten with an understanding of which parts of the selector refer to things in the host context and which parts of the selector refer to things in the shadow DOM
  • Requires injecting amp-custom styles, amp-runtime styles, and amp-extension="*" styles into every frame

Dynamically insert/remove <style> tags, keeping only one active

Pros

  • It's easy, I guess

Cons

  • It doesn't really solve the problem because this applies at the document level rather than the amp-story-page level; if there are two content pages visible and one ad page visible, which style tag gets inserted?
  • Could cause a lot of relayout

@newmuis
Copy link
Contributor Author

newmuis commented Jun 14, 2018

One issue that this has is that it will not work for selectors that select elements that are ancestors of the amp-story-page, or the amp-story-page itself.

One way around this is to include the original style and an inverted variant of the selector. For example, the selector p would become .i-amphtml-story-id-foo p, p .i-amphtml-story-id-foo. This works, but may be computationally expensive, particularly for styles that were already inefficient (like those containing *). We could potentially just rewrite problematic selectors further (i.e. * .i-amphtml-story-id-foo is equivalent to .i-amphtml-story-id-foo based on assumptions we can make about the document).

@newmuis
Copy link
Contributor Author

newmuis commented Jun 14, 2018

This option also breaks, because we'd need to have a deeper understanding of the hierarchy of the document. For example amp-story p would, by default, become .i-amphtml-story-id-foo amp-story p, amp-story p .i-amphtml-story-id-foo, and neither of those selectors are correct (the closest "intended" variant of the selector would be amp-story .i-amphtml-story-id-foo p).

Another option is to do nothing beyond the original proposal and require publishers to write their style in a way that is compatible (i.e. always specify styles on amp-story-page elements or their descendants). This may be more correct semantically, especially if we would like to consider treating pages as a unit and/or splicing pages from one story into another story. However, it is also more difficult to warn publishers about. One way to warn is that in development mode, we could query for the selectors at runtime and assert that they are not targeting anything that is an ancestor of amp-story-page, but even this is not foolproof, as what gets matched by a selector can depend on a number of factors (e.g. viewport size)

@jaygray0919
Copy link

Would you consider amp-specific directives. For example, you have amp-img as the amp-specific implementation of img. Perhaps you implement amp-p for a paragraph. etc. As authors, we are 'thinking' amp-story: how to compose content to fit the design. We may wish to reuse content from another source ( text and images another page, etc.) but know that the source content has to be designed to fit the amp-story design.

@newmuis
Copy link
Contributor Author

newmuis commented Jun 20, 2018

After today's design review (#15347), the feedback is to continue with the approach of dynamically rewriting the <style> tag within the document, but resolve my comments above by not doing any scoping at all and solving that problem separately, relying strictly on shadow DOM. For now, we will assume all stories to be standalone as well.

@jaygray0919, I don't think we would want to go the route of rolling a new component for each HTML element, but rather (where possible) we'd like to make existing HTML components work as well as possible in our system. As such, we want to respect existing responsive design techniques, as well as enabling reasonable defaults (like making the font-size responsive by default).

@newmuis newmuis changed the title Make stories respect responsive units [Master feature] Make stories respect responsive units Jul 2, 2018
@erwinmombay
Copy link
Member

This is a high priority issue but it hasn't been updated in awhile. @newmuis Do you have any updates?

@newmuis
Copy link
Contributor Author

newmuis commented Aug 21, 2018

em and rem are now defined (at the story level) to be responsive to the size of the page, but the amp-story-responsive-units experiment to enable rewriting of publisher-specified responsive styles, media queries, and srcset rules using viewport-based units is still in progress.

@ampprojectbot
Copy link
Member

This is a high priority issue but it hasn't been updated in awhile. @newmuis Do you have any updates?

2 similar comments
@ampprojectbot
Copy link
Member

This is a high priority issue but it hasn't been updated in awhile. @newmuis Do you have any updates?

@ampprojectbot
Copy link
Member

This is a high priority issue but it hasn't been updated in awhile. @newmuis Do you have any updates?

@newmuis
Copy link
Contributor Author

newmuis commented Nov 30, 2018

This experiment has launched, but there is a bug causing it to not be surfaced (the custom styles are placed on the <amp-story> element, while the base font size is specified on the <html> element). We'll need to fix this issue, and roll it out carefully as a new experiment, so as to minimize breakage of existing stories.

@jaygray0919
Copy link

Thanks for update. We have several stories pending the implementation of this feature so will be ready to test when you signal the next release. We also experience problems with some SVGs and will test those conditions in next release.

@newmuis
Copy link
Contributor Author

newmuis commented Nov 30, 2018

@jaygray0919 sounds good. Please feel free to file a separate issue for the SVG questions you may have.

@ampprojectbot
Copy link
Member

This is a high priority issue but it hasn't been updated in awhile. @newmuis Do you have any updates?

1 similar comment
@ampprojectbot
Copy link
Member

This is a high priority issue but it hasn't been updated in awhile. @newmuis Do you have any updates?

@newmuis
Copy link
Contributor Author

newmuis commented Feb 25, 2019

Ultimately, we took a different approach here, but this was implemented in #16378 and #20981 should put it into effect on all stories using 1.0.

@newmuis newmuis closed this as completed Feb 25, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants