1.0.4 • Published 2 years ago

@wranggle/snapped-page v1.0.4

Weekly downloads
-
License
BSD-3-Clause
Repository
github
Last release
2 years ago

@wranggle/snapped-page

This lib helps your browser page communicate with the PuppySnap service as it creates video frames from the animations run on your page.

Typically the PuppySnap service runs as part of some grander workflow. (Eg, updating a dashboard; burning a bottom-third intro onto a video; a user sharing a gif for lolz; etc)

See also @wranggle/snapped-page-react

Installation

npm i --save @wranggle/snapped-page

# or

yarn add @wranggle/snapped-page

Recording animations

const animationHelper = ensureGlobalSnappedPageHelper();
const animationPageData = await animationHelper.resolveAnimationPageData();
await animationHelper.startPuppySnapRecording();
startMyAnimation({ 
  ...animationPageData, 
  someAnimationCompleteCallback: () => animationHelper.stopPuppySnapRecording() 
});
// ^^^ make sure stopPuppySnapRecording is called when animation finishes

Important: call animationHelper.stopPuppySnapRecording() when finished, or the service will continue to record until a time or space limit is hit and will fail.

animationPageData

The PuppySnap service job payload includes a pageData entry, which is populated during runtime by previous workflow steps, then shared with your page when requested using the animationHelper.resolveAnimationPageData function.

You define the shape of this object, it's the data you need to render your animation, such as the text to be animated, user color/style preferences, etc.

You can also provide this animationPageData to the page in a few other ways, mainly to assist dev-mode and for test fixtures.

The helper will resolve animationPageData with the first to qualify of the following:

  1. Return value of optional resolvePageData callback
  2. PuppySnap job data
  3. Data embedded in page's URL
  4. Data set on window.animationPageData_devMode (ignored if PuppySnap is present)
  5. Nothing, animationPageData is set to false
  • window.animationPageData_devMode: this is a quick & dirty but convenient option for trying out your animation with specific fixture data while you're actively banging on the src. Eg: window.animationPageData_devMode = { textLine: 'sparse content' }. The data is ignored when running within the PuppySnap service, so if you forget to remove it from the src (doops) it won't show up in a production video.

  • Embedded in URL. Useful for holding multiple test/data fixtures on another page. You can encode page data in links:

    import { SnappedPageHelper } from "@wranggle/snapped-page-react";
    const hrefWithAnimationPageData = (data) => `${ someBaseUrl }?animationPageData=${ SnappedPageHelper.encodePageDataForQsParam(data) }`;
    
    // then you might create a fixture page with links, eg:
    // <a href={ hrefWithAnimationPageData(MyFixtures['colorful']) } target="_blank">colorful version</a>
  • Custom callback: See resolvePageData in options section below. It is passed all of the potential values it finds and is expected to return the final value when the callback is provided.

Important Considerations

viewport size

PuppySnap is passed the screen viewport size at runtime as part of its job data. Animations of charts or data visualization might use consistent, fixed sized squares or rectangles, to be positioned or scaled in a subsequent step as they are placed onto the video. In contrast, PuppySnap might be told use the full size of the user's video, measured in a previous step, for something like an animated comet streaking across the screen. In this case, your page might be used to create animated overlays in very different sizes or aspect rations, like 480x720 or 4096x2160.

Suggestions:

  • In your css, use relative units like vw and vh (percent of viewportWidth and viewportHeight) rather than px or other fixed sizes. Especially when the page is dedicated to creating PuppySnap animations without needing to consider user interactions or accessibility considerations.

  • For full-video animations, consider using orientation @media queries in your css, or a responsive layout. Or if you don't feel like optimizing for both the big screen and the big phone, start with a square aspect ratio.

  • Keep in mind that you can include size or placement options when defining the shape of your animationPageData.

Recording in slow motion

Browsers running on even powerful hardware frequently drop animation frames and they intentionally use imprecise timing to reduce their vulnerability to Spectre timing attacks. To improve animation smoothness during recording, and to support high user-defined frame rates, PuppySnap uses timesnap-core to modify clock time within the page while recording. Related considerations for you to keep in mind:

  • The lib is not currently compatible with @keyframes / css-based animations or animation libraries relying on them; there are plenty of javascript-based animation libraries that will work such as react-spring, popmotion, greensock, anime.js, etc. Any using a javascript-based approach for timing (including requestAnimationFrame) will work.
    But some very nice ones based on the newer Web Animation API (like motion.dev, animate.style) won't work at the moment--see tech notes below.

  • in-page timestamps won't correspond with the wall clock

See tech notes on this subject below.

One recording per page load

Recording an animation follows a strict sequence: 1) your page loads and prepares any fonts or external data that it needs; 2) it signals to start the recording; 3) it signals to stop the recording.

See PuppySnap for details/explanations and info around timeouts.

Additional options

  • resolvePageData. Custom callback function, sync or async, that return the final animationPageData value. It is passed values from all three possible pageData sources, as { pageDataFromPuppySnap, pageDataFromUrl, pageDataFromGlobal } It needs to be set early, before the helper tries to resolve animationPageData. A safe approach is setting it before components render with:
import { ensureGlobalSnappedPageHelper } from "@wranggle/snapped-page-react";
ensureGlobalSnappedPageHelper({ resolvePageData: ({ pageDataFromPuppySnap, pageDataFromUrl, pageDataFromGlobal }) => myPageData }); 
  • event bindings (snapped-page-constants.ts)

Tech notes: keyframe/css animations

There may be opportunities to offer css-based animations in the future. Haven't looked deeply but supporting the full Web Animation API looks doable. Some tech notes:

  • Stop-Motion mode might be automated to sync each animation timeline. (Worth a quick try)

  • It might be feasible for PuppySnap to automatically control the animation timeline at a lower level using Chromium's developer Animation tools by watching for animation-created events, pausing them, then seeking to the desired point in time for each frame recording.

  • The timesnap author lists it as a potential improvement for the project, using an in-page javascript approach.

Stop-Motion Mode (wip)

Before each frame is recorded, PuppySnap sends the next frame number and the fps to the page, emitted on snappedPageHelpe as PuppySnapPageEventName.RecordingNextFrame.

When the PuppySnap job is provided with stopMotionOpts, it waits for the page to explicitly signal rendering is ready before it records this next frame.

To use, listen for PuppySnapPageEventName.RecordingNextFrame, render the page/animation, and send the ready signal. Eg:

animationHelper.on(PuppySnapPageEventName.RecordingNextFrame, async () => {
  await myRenderFn();
  await animationHelper.stopMotionFrameReady();
});

Note: Something similar could be used to feed Animation.currentTime. (Would prob want to add a new signal function for this, so PuppySnap waits until the page explicitly says the next frame is ready.)

To maybe try for keyframe animations: document.getAnimations() and set timeline using current frame.