@wranggle/snapped-page
v1.0.4
Published
Helps browser-side code communicate with the puppy-snap service to record animations
Downloads
3
Readme
@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:
- Return value of optional
resolvePageData
callback - PuppySnap job data
- Data embedded in page's URL
- Data set on window.animationPageData_devMode (ignored if PuppySnap is present)
- 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
andvh
(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.