Implementing Interactivity

The baseline experience after enabling AMP on an existing theme is that it largely behaves as if JavaScript is disabled in the user’s browser. Many themes and plugins already have fallbacks in case JavaScript is not available so that will serve them well in AMP. In fact, while custom script tags are stripped from the response, any noscript elements will automatically get unwrapped in the response so their contents will be used in AMP (as of v1.0-beta1, see #1226). For example, given this original HTML:

<script src="https://example.com/script.js"></script>
<noscript><iframe src="https://example.com/fallback/" width="200" height="200"></noscript>

The noscript will be unwrapped in AMP as:

<amp-iframe src="https://example.com/fallback/" width="200" height="200" sandbox="allow-scripts allow-same-origin" layout="intrinsic" class="amp-wp-enforced-sizes"></amp-iframe>

Therefore, most of the work entailed is to progressively enhance a theme for AMP to restore features that were lost during this graceful no-JS degradation to AMP. These enhancements for AMP generally involve the use of modern CSS selectors (e.g. :focus-within), AMP components (e.g. amp-carousel and amp-position-observer), or AMP scripting via amp-bind. With these you can achieve full feature parity in AMP with the same interactivity you expect normally in a theme. This page documents some of the common solutions. In the following example is used as the theme slug/prefix.

Skip Enqueueing JavaScript

First of all, since custom JavaScript isn’t allowed in AMP you should avoid enqueueing it in the first place in order to avoid having to accept validation errors for invalid script elements. Likewise, you should skip outputting any inline scripts like this common example_javascript_detection():

/**
 * Handles JavaScript detection.
 *
 * Adds a `js` class to the root `<html>` element when JavaScript is detected.
 * This function is a no-op in AMP since custom JavaScript is not allowed.
 */
function example_javascript_detection() {
    if ( example_is_amp() ) { //  Added.
        return;
    }
    echo "<script>(function(html){html.className = html.className.replace(/\bno-js\b/,'js')})(document.documentElement);</script>\n";
}
add_action( 'wp_head', 'example_javascript_detection', 0 );

The example_is_amp() function here is simply a wrapper around is_amp_endpoint():

/**
 * Determine whether this is an AMP response.
 *
 * Note that this must only be called after the parse_query action.
 *
 * @return bool Is AMP endpoint (and AMP plugin is active).
 */
function example_is_amp() {
    return function_exists( 'is_amp_endpoint' ) && is_amp_endpoint();
}

This conditional function will be re-used in examples below.

Themes will often enqueue scripts and styles in the same function which is called
at the wp_enqueue_scripts action. It makes sense to create two separate functions
for enqueueing the scripts and styles so that you can short-circuit the former in AMP:

/**
 * Enqueue styles.
 */
function example_styles() {
    wp_enqueue_style( 'example-style', get_stylesheet_uri() );
}
add_action( 'wp_enqueue_scripts', 'example_styles' );

/**
 * Enqueue scripts (if not AMP).
 */
function example_scripts() {
    if ( example_is_amp() ) {
        return;
    }

    wp_enqueue_script( 'example-global', get_theme_file_uri( '/assets/js/global.js' ), array( 'jquery' ), '1.0', true );

    // ...
}
add_action( 'wp_enqueue_scripts', 'example_scripts' );

Use Built-in AMP Actions

AMP has built-in actions and events which you can use by adding an on attribute to elements, similar to how you would add event handlers in JavaScript. In Twenty Seventeen, for example, there is a button in the nav bar on the homepage that smooth-scrolls you down to the first panel. In the core theme this is implemented by requiring the jQuery scrollTo plugin, jQuery itself, and then logic in the theme’s global.js to use the plugin:

// Non-AMP solution:
$( '.menu-scroll-down' ).click( function( e ) {
    e.preventDefault();
    $( window ).scrollTo( '#primary', {
        duration: 600,
        offset: { top: menuTop - navigationOuterHeight }
    });
});

So there are three blocking scripts to achieve this one effect. In AMP this capability is built-in to the runtime so there is no need for any of these external script dependencies:

<!-- AMP solution: -->
<a href="#content" class="menu-scroll-down" on="tap:primary.scrollTo(duration=600)">
    <span class="screen-reader-text">Scroll down to content</span>
</a>

References

  • Pull request for adding AMP support for core themes, including more theme-specific features than are covered in this page.
  • Pull request for adding AMP support to the underscores starter theme.
  • WP Rig which has built-in support for AMP.