Crawler
If you are not familiar with the concept of middleware, make sure to check the middlewares explanation. Also, make sure that you are familiar with the Actions concept.
This example interacts with the Actions API that will be re-designed in the future (keyframes). This example will be regularly updated so that it is always up-to-date with the latest version of the Loopic.
Crawler is one of the most popular ways to show text messages to the viewers. However, despite looking very simple and straightforward, implementing crawler animation in HTML has always been tricky and was almost impossible to do without 3rd party animation library. This guide will demonstrate how you can create advance crawler animations just by using Loopic - without any 3rd party libraries!
Prerequisites
For this example, you'll need one Composition with just one single Text element with the _text
key. Make sure that the Text element has "auto size" option turned on since it will be important for calculating the length of the text. Duration of the Composition is not important since it will be dynamically changed using the Actions API.
The logic
Alright, so how do we implement a crawler? Should you set the Composition duration to, for example, 100 frames, and animate text going from left to right? Well, that would work if you had only static text. But when changing the text dynamically, we don't know how long the text will be, between which positions exactly it should animate, and how fast it should go.
To begin with, we'll create a new middleware that will be triggered each time our _text
receives new content. This middleware should be added inside the Composition action, and that will make sure that our middleware is registered before it receives any new text content.
loopic.useOnUpdate("_text", (key, value, next) => {
// Middleware code
});
Now inside the middleware, we should get the reference to the text element and get it's width. We'll also store the width of the composition and our desired crawler speed in dedicated variables. But be careful here - we want to get the width of the text content that has just been sent to the template, which means that we should first set the content of the Text element. The default text middleware (which is in this case referenced with the next
variable) can do that job for us.
next();
const SPEED = 200;
const compWidth = 1920;
const textEl = this.findElementByKey("_text");
const textWidth = textEl.style.width.value;
Now let's see between which X positions our Text element should animate. startX
contains the starting X position of the Text element, finalX
contains the final X position of the Text element, totalDistance
is the distance (in pixels) which our Text element will travel, and duration
contains information about how long our animation should be. Duration is not fixed here, because the duration for crawling very short text and very long text is different - very long text needs much more time to travel between the starting and final positions.
const startX = compWidth + textWidth / 2;
const finalX = -textWidth / 2;
const travelDistance = textWidth + compWidth;
const duration = parseInt((travelDistance / SPEED) * this.fps);
Once we know our new duration of the animation, we need to change the duration of the Composition, as well as the duration of the layer which contains the Text element.
textEl.layer.duration = duration;
this.duration = duration;
And we are almost done - now we just need to add keyframes to our animation.
Adding keyframes
The code below will delete any existing keyframes on the X-style property and it will add two new keyframes for animating our crawler. In the end, we'll need to trigger the compute
method of the composition to tell the Loopic that something has changed and that it needs to recompute all the animations.
textEl.style.x.keyframes = [];
textEl.style.x.keyframes.push({
id: "start",
_value: startX,
frame: 0,
easing: { p1x: 1, p1y: 1, p2x: 1, p2y: 1 },
});
textEl.style.x.keyframes.push({
id: "final",
_value: finalX,
frame: duration,
easing: { p1x: 1, p1y: 1, p2x: 1, p2y: 1 },
});
this.compute();
Final code
Here is what the final middleware inside the Composition action should look like.
loopic.useOnUpdate("_text", (key, value, next) => {
const SPEED = 200;
const compWidth = 1920;
const textEl = this.findElementByKey("_text");
textEl.setContent(value);
const textWidth = textEl.style.width.value;
const startX = compWidth + textWidth / 2;
const finalX = -textWidth / 2;
const travelDistance = textWidth + compWidth;
const duration = parseInt((travelDistance / SPEED) * this.fps);
textEl.layer.duration = duration;
this.duration = duration;
textEl.style.x.keyframes = [];
textEl.style.x.keyframes.push({
id: "start",
_value: startX,
frame: 0,
easing: { p1x: 1, p1y: 1, p2x: 1, p2y: 1 },
});
textEl.style.x.keyframes.push({
id: "final",
_value: finalX,
frame: duration,
easing: { p1x: 1, p1y: 1, p2x: 1, p2y: 1 },
});
this.compute();
});
Bonus: Looping
Quite often, you want your crawler to keep looping. To achieve this, we will use the setInterval
function, which starts the crawler animation every time it reaches the end.
The final code with looping now looks like this:
let timer;
loopic.useOnUpdate("_text", (key, value, next) => {
if (timer) {
clearInterval(timer);
}
next();
const SPEED = 200;
const textEl = this.findElementByKey("_text");
const textWidth = textEl.style.width.value;
const coreElement = this.findElementByKey("_core");
const coreComp = coreElement.composition;
const coreWidth = coreElement.style.width.value;
const startX = coreWidth + textWidth / 2;
const finalX = -textWidth / 2;
const travelDistance = textWidth + coreWidth;
const duration = parseInt((travelDistance / SPEED) * this.fps);
textEl.layer.duration = duration;
coreComp.duration = duration;
textEl.style.x.keyframes = [
{
id: "start",
_value: startX,
frame: 0,
easing: { p1x: 1, p1y: 1, p2x: 1, p2y: 1 },
},
{
id: "final",
_value: finalX,
frame: duration,
easing: { p1x: 1, p1y: 1, p2x: 1, p2y: 1 },
},
];
coreComp.compute();
const durationInSeconds = duration / this.fps;
const startAnimation = () => {
coreComp.goToAndPlay(0);
};
startAnimation();
timer = setInterval(startAnimation, durationInSeconds * 1000);
});
loopic.useOnStop(() => {
if (timer) {
clearInterval(timer);
}
this.play({ reverse: true });
});
If you have any questions or suggestions regarding this example, feel free to contact us at info@loopic.io.