Git Product home page Git Product logo

turn's Introduction

Turn

Animate page transitions in Turbo Drive apps.

Installation

Install @hotwired/turbo, then

npm install @domchristie/turn

Usage

  1. Include Turbo, dist/turn.js and dist/turn.css however you build your JavaScript & CSS
  2. Add data-turn-enter and data-turn-exit to the elements you wish to animate (optional if you're only using View Transitions)
  3. import Turn from '@domchristie/turn' and call Turn.start() in your application JavaScript
  4. Navigate between pages โ€ฆ โœจ

Customizing Animations

Turn adds turn-before-exit, turn-exit, and turn-enter classes to the HTML element at the appropriate times. Apply your own animations by scoping your animation rules with this selector. For example:

html.turn-advance.turn-exit [data-turn-exit] {
  animation-name: MY_ANIMATE_OUT;
  animation-duration: .3s;
  animation-fill-mode: forwards;
}

html.turn-advance.turn-enter [data-turn-enter] {
  animation-name: MY_ANIMATE_IN;
  animation-duration: .6s;
  animation-fill-mode: forwards;
}

@keyframes MY_ANIMATE_OUT {
  โ€ฆ
}

@keyframes MY_ANIMATE_IN {
  โ€ฆ
}

This is how turn.css is organized, so you may want to get rid of that file altogether. animation-fill-mode: forwards is recommended to prevent your transitions from jumping back.

Custom Class Names

The values set in the data-turn-exit and data-turn-enter attributes will be applied as class names to that element. This lets you customize animations for each element. Styles should still be scoped by html.turn-exit and html.turn-enter.

Class List

The following class names are added to the HTML element at various time during a navigation lifecycle.

turn-view-transitions

Added on start if the device supports View Transitions, and View Transitions are enabled.

turn-no-view-transitions

Added on start if the device does not support View Transitions, or View Transitions are disabled.

turn-advance

Added when a visit starts and the action is advance. You'll probably want to scope most animations with this class. Removed when the navigation has completed and all animations have ended.

turn-restore

Added when a visit starts and the action is restore. Given the number of ways it's possible to navigate back/forward (back button, swipe left/right, history.back()), it's generally recommended that restoration visits are not animated. Removed when the navigation has completed and all animations have ended.

turn-replace

Added when a visit starts and the action is replace. Removed when the navigation has completed and all animations have ended.

turn-before-exit

Added when a visit starts. Useful for optimising animations with will-change. (See turn.css for an example.) Removed when the exit animations start.

turn-exit

Added when a visit starts. Use this to scope exit animations. Removed when the exit animations complete.

turn-before-transition (if View Transitions supported & enabled)

Added after the exit animations and any request has completed, but before the View Transitions have taken their snapshot. Useful when combining View Transitions with custom exit/enter animations. To avoid a flash of content, use this class to target exited elements, and style them in their final "exit" state. (See turn.css for an example.) Removed when the transition starts.

turn-transition (if View Transitions supported & enabled)

Adding during a View Transition. Useful when combining View Transitions with custom exit/enter animations.

turn-enter

Added after any requests have completed and previous animations/transitions have completed. Removed once the animations have completed.

Events

event.details may contain:

  • action: the action of the visit (advance or restore)
  • initiator: the element the visit was triggered from (an a, form, or html element if a Back/Forward navigation)
  • referrer: the URL the page is transitioning from
  • url: the URL the page is transitioning to
  • newBody: the incoming <body> that will be transitioned to

turn:before-exit

Dispatched before exit animations are started. event.detail includes:

  • action
  • initiator
  • referrer
  • url

turn:before-transition

Dispatched before a View Transition is started (after exit animations if present). Ideal for setting up view-transition-names before the View Transition performs its capturing. event.detail includes:

  • action
  • initiator
  • newBody
  • referrer

turn:before-enter

Dispatched before enter animations are started (after Vire Transitions if present). event.detail includes:

  • action
  • initiator
  • newBody
  • referrer

turn:enter

Dispatched after the all transitions and animations have completed. event.detail includes:

  • action
  • referrer
  • url
  • timing: the visit's timing metrics

Usage with Tailwind CSS

Define animations in tailwind.config.js, and add a plugin that scopes the styles, e.g.:

const plugin = require('tailwindcss/plugin')

module.exports = {
  theme: {
    extend: {
      animation: {
        exit: 'fade-out-up 0.3s cubic-bezier(0.65, 0.05, 0.35, 1) forwards',
        enter: 'fade-in-up 0.6s cubic-bezier(0.65, 0.05, 0.35, 1) forwards'
      },
      keyframes: {
        'fade-out-up': {/* โ€ฆ */},
        'fade-in-up': {/* โ€ฆ */}
      }
    }
  },

  plugins: [
    plugin(function ({ addVariant }) {
      addVariant('turn-exit', 'html.turn-exit &')
      addVariant('turn-enter', 'html.turn-enter &')
    })
  ]
}

Then in your HTML:

<main data-turn-exit="turn-exit:animate-exit" data-turn-enter="turn-enter:animate-enter">
  <!-- โ€ฆ -->
</main>

Disabling Animations

Add data-turn="false" to the <body> to opt out of animations from that page.

(This currently rather basic, but is limited by the information available in Turbo events. Hopefully improvable if turbo:visit events are fired on the initiating element.)

Tip & Tricks

1. Animate Changes

Avoid animating the whole body. Animations should target elements that change on navigation. So avoid animating persistent headers and instead animate the main element or just the panels/cards within it.

2. Nesting

Nesting animating elements draws attention and brings screens to life. Add data-turn-exit/data-turn-enter attributes to elements such as headings and key images within an animating container. The compound animation effects means they'll exit faster, and enter slower than other elements. For example:

<main data-turn-exit data-turn-enter>
  <h1 data-turn-exit data-turn-enter>Hello, world!</h1>
</main>

3. Optimizing Animations

Jumpy exit animations can be prevented using the will-change CSS property. Turn adds a turn-before-exit class to the HTML element just before adding the exit classes. This provides an opportunity to notify the browser of upcoming changes. For example, by default turn.css does the following:

html.turn-before-exit [data-turn-exit],
html.turn-exit [data-turn-exit] {
  will-change: transform, opacity;
}

4. Loading Spinner

Exit animations on slow requests can leave users with a blank screen. Improve the experience with a loading spinner that appears a short time after the exit animation. For example, if your exit animation take 600ms, add a spinner that starts appearing 700ms after that by using transition-delay. This spinner can live permanently in the body and only transition when the turn-exit class is applied:

.spinner {
  position: fixed;
  top: 15%;
  left: 50%;
  transform: translateX(-50%);
  opacity: 0;
  transition: opacity 100ms;
}
html.turn-exit .spinner {
  opacity: 1;
  transition-delay: 700ms
}

Not seeing animations?

Check your device preferences to see if you have requested reduced motion. Turn will only animate transitions when the prefers-reduced-motion media query does not match reduce.

How does it work?

Turn adds exit and enter classes at the appropriate times like so:

  1. on turbo:visit add the exit classes
  2. pause turbo:before-render (wait for exit animations to complete before resuming)
  3. on turbo:render, remove exit classes and add the enter classes
  4. on turbo:load, wait for the enter animations to complete before removing the enter classes

Credits

Default fade in/out animations adapted from Jon Yablonski's Humane By Design.

License

Copyright ยฉ 2021+ Dom Christie and released under the MIT license.

turn's People

Contributors

domchristie avatar nacoliu avatar yummyume avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

turn's Issues

Only animate elements inside a frame

Hello, thanks for making this library, it's a blast ๐Ÿ”ฅ

One thing I am wondering is, would it be possible to only animate the elements inside a Turbo frame (when navigation or form submit happens and targets a frame)?

From what I understand, it doesn't look like it's possible at the moment looking at the disabling animations section. But would it be possible once that is fixed? It would help a lot as right now, the only solution I found is to disable Turn when the navigation starts, show some skeleton and then re-enable Turn when the frame is rendered.

By the way, I think there's a typo in the Tailwind usage :

<main data-turn-exit="turn-exit:animate-exit" data-turn-enter="turn-exit:animate-enter">

should be

<main data-turn-exit="turn-exit:animate-exit" data-turn-enter="turn-enter:animate-enter">

Temporary flash of new content

When using non-view-transition animations alone, there's a temporary flash of the new content before enter. Current solution is to use v2.1.0 (where view transitions are disabled by default)

[BUG] Transition hangs after 400~500 page visit

Hello! It's me again.

I updated to the latest version and really decided to try out the view transition API. It works very well and looks very smooth.

However, I encountered a weird behaviour. After visiting a 400~500 status code page (like a 404 page), future navigation will hang for a long time (a few seconds) during which I cannot do anything on the page. It's definitely not a speed problem, as the networks tab completes instantly. Removing Turn also gets rid of the problem. I created a new quick Symfony app and tried it without any other package and with default transitions, and the problem persists. I also tried to set Turn.config.experimental.viewTransitions = false; but it doesn't seem to do anything? It's still using the view transition API, I believe (dunno if this is intended).

Looking in the DevTools, it seems that the transition API tries to animate something? Because I can see the ::view-transition appear while it hangs. It's also worth noting that it does not happen when I visit the 404 page directly from my browser and then click on the homepage. It only happens when Turbo visits the 404 link.

If needed, I can create a quick repository to highlight what I did exactly and what the problem is (I only tested it in Symfony apps, but I doubt it's a problem with it, I just built the assets normally using Webpack).

Possible improvements with the view transition API

Hello ๐Ÿ™‚

I started using Turn with the view transition API and I noticed a few things that could maybe be integrated into the library, let me know what you think (I am still learning how the API works and its best practices, but I think I have a pretty good understanding of it already).

1. Add an attribute helper to add view-transition-name on elements during transitions

I think one of the best features of the view transition API is the ability to let the browser automatically translate elements from one place to another using view-transition-name. From what I understand (and what I've tested so far), it's important to have this attribute added only during the transition and then removed if the DOM elements are not the same (which will be the case 99% of the time during visits). This is explained here.

With that, I think it's in the scope of this library to add an attribute like data-turn-transition-name that would only get added to its respective element during a view transition, and then automatically removed (by setting the view-transition-name property to an empty string or none).

It would also maybe be interesting to have it "scoped" to its current element, so it does not add the view-transition-name property on every element on the page? I don't know what the best practises for names are. e.g. I have a list of products, do I just add view-transition-name: product-title; to the product I clicked on during the transition, or add view-transition-name: product-title-1;, etc... On each product currently visible on my page?

I did a quick implementation with the current events dispatched by Turn, I have no idea if this is the best way to do it and it could probably get some improvement, but it worked pretty well during my testing.

let pendingTransitions: HTMLElement[] = [];

// Add transition name to elements with data-turn-transition-name attribute (only those inside the initiator)
window.addEventListener('turn:before-transition', (({ detail }: CustomEvent<{ initiator: HTMLElement }>) => {
  if (getPrefersReducedMotion()) {
    return;
  }

  const { initiator } = detail;
  const transitions = initiator.querySelectorAll('[data-turn-transition-name]');

  transitions.forEach((transition) => {
    if (!(transition instanceof HTMLElement)) {
      return;
    }

    // @ts-expect-error Experimental API
    transition.style.viewTransitionName = transition.dataset.turnTransitionName;

    pendingTransitions.push(transition);
  });
}) as EventListener);

// Remove the transition names we added before
window.addEventListener('turn:before-enter', (() => {
  pendingTransitions.forEach((transition) => {
    // @ts-expect-error Experimental API
    transition.style.viewTransitionName = 'none';
  });

  pendingTransitions = [];
}) as EventListener);

With that, my details page would have the transition names set

<h1 style="view-transition-name: news-title;">Some news</h1>

<p style="view-transition-name: news-desc;">News description<p>

And my results on my search page would have the data- attribute with the same name

<a href="news/some-news" class="news__result">
  <p data-turn-transition-name="news-title">Some news</p>
  <p data-turn-transition-name="news-desc">News desc...</p>
</a>

The title and description now nicely translate when clicking on the link or going back to the search page.

2. Allow hybrid configuration for Firefox/Safari

I found myself in a situation where I had to make a choice : either support only view transition browsers and hope Firefox/Safari implement it soon enough, or give up on it and use data-turn-enter & data-turn-exit only.

This happened because on a page with a navbar header, a footer and a sidebar (lots of navigation), I wanted these elements to not animate during a visit, so I would add data-turn-enter & data-turn-exit to the container with the current view. It works well but breaks the view transition API completely. It seems that as soon as I add these attributes, the view transitions are not taken into account at all (most likely because they conflict with each other as Turn is animating the whole view with a fade). It would be interesting to be able to add these attributes, but only use them if the view transition API is not available.

I know what I want to achieve would most likely be achievable by using CSS only rather than the data- attributes, but it would require more work than simply adding my Tailwind animation to the attribute.

Failing turbo visits

I found that when I added turn to my rails 7 project, it worked until page transitions were too quick. So I removed some classes on html element when initialising the Turn class instance.

And also added export default on the class in order to import it in my application.js

There may be a better solution to this, I'm just adding my contribution :)

export default class Turn { constructor (action) { // ... this.removeClasses('enter'); this.removeClasses('exit'); }

support ts or cjs

@domchristie/turn doesn't appear to be written in CJS, but also doesn't appear to be a valid ES module (i.e. it doesn't have "type": "module" or an .mjs extension for the entry point). Please contact the package author to fix.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.