Custom Animation

Write a custom animation that moves smoothly from one image to another, zooming out in the middle for a nice overview

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

This lesson preview is part of the OpenSeadragon Deep Dive course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to OpenSeadragon Deep Dive, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

Thumbnail for the \newline course OpenSeadragon Deep Dive
  • [00:00 - 00:10] In the previous lesson, you learned how to zoom in on a single image using the default spring animation. In many cases, this is all you'll need, but sometimes it's nice to be able to create your own custom animations.

    [00:11 - 00:25] You might want to add the ability to go to a specific image from wherever the user happens to be zoomed in pan 2. You can use the standard OpenC Dragon fitbounds to animate to the new location, but that can be a very fast transition if you're going long distances.

    [00:26 - 00:38] You can write your own animation if you have something smoother in mind. In this lesson, we'll do just that. In our case, let's just add a random image button. For starters, we'll use the built-in OpenC Dragon animation, and then we'll write our own.

    [00:39 - 00:45] Drop a button in our toolbar in the HTML. Let's add a little margin to our buttons so they're not jammed up against each other.

    [00:46 - 00:53] Now hook up a handler for the new button in our main function. This should look pretty familiar, it's very similar to the code for animating to an image that you've clicked on.

    [00:54 - 01:03] Give it a try, it should whip you around the viewer at a good clip. Keep in mind that since it's completely random, sometimes it won't do anything because it's already on the image that it chose to go to.

    [01:04 - 01:21] To create a custom animation, we need to introduce our own animation system, and each frame will tell OpenC Dragon where to go, always using the Immediately flag so it doesn't try to animate as well. For doing custom animations, I recommend the tween.js library. It's nice and simple, but gets the job done.

    [01:22 - 01:31] To add it to the project, we need to add it to the HTML. In the JavaScript, to support the animation, we need to create a request animation frame loop that repeatedly calls tween.update.

    [01:32 - 01:43] In production, you would want to only do this loop when actually animating, but for this example, we'll just let it run all the time. When there aren't any tweens, tween.update is quite fast, so it's not a real performance problem.

    [01:44 - 01:53] tween.js basically has one mission to change in objects properties from their starting values to some new values over time. Here's a very simple example.

    [01:54 - 02:05] We create a point object with x and y of 0, 0. We then create a tween without object and specify that it should animate to x and y of 100 and 200, over the next 5,000 milliseconds.

    [02:06 - 02:16] We give it a function to get called periodically as it animates, and we log the current value to the console. Then we tell the tween to start. That's enough to make it work, since we have already set up the update loop above.

    [02:17 - 02:25] That's pretty much all you need to know for this lesson, but if you're interested in learning more about tween.js, check out its documentation. Okay, back to our app.

    [02:26 - 02:40] What we want to create is a nice smooth pan from image to image, zooming out some in the middle to give the user an overview of where they're going, and to make it so it's not just a close-up of the images flying by. We'll want to build this animation up. Let's start with just the panning portion.

    [02:41 - 02:47] Get rid of the fit bounds and instead create a tween. This is very similar to the example tween, with a few differences.

    [02:48 - 02:59] For the tween to object, we're just passing in the result of view bounds get center, which creates a new open-c-dragon point. Remember that tween.js modifies the object you give it, so you have to be sure you don't mind the object changing.

    [03:00 - 03:12] In this case, it's no problem, since get center creates a new object. We didn't put the get center result into a variable that we could access inside the on update, but that's not a problem because on update passes the tween object into our update function.

    [03:13 - 03:20] We're asking it to apply an easing. This makes the animation vary at speed throughout its duration to create a more naturalistic effect.

    [03:21 - 03:32] Because the easing we supplied is an in/out easing, the animation will start off slowly and build up speed, then slow back down at the end. Of course, instead of console logging, we're actually using the point to pan the viewer.

    [03:33 - 03:40] Note that we pass true for the immediately argument of the pan2 function. This disengages the springs, since we are handling the animation ourselves.

    [03:41 - 03:49] So, as you can see, we're panning from the center of the current viewport bounds to the center of the image bounds over the course of five seconds. Go ahead and give it a try.

    [03:50 - 04:00] Notice that it's only panning and not zooming at all. This means that if you're all the way zoomed out when you hit the random image button, then it'll stay zoomed out, but move so the new image is centered on the screen .

    [04:01 - 04:09] If you're already zoomed in on an image, it'll move you across to the new image , but it won't zoom you in or out to properly frame the new image. So, we need to add zooming to this animation.

    [04:10 - 04:15] The first step is to learn where we're zooming from and to. Where we're zooming from is easy. It's the current zoom.

    [04:16 - 04:24] Where we're zooming to is a little trickier. We want to zoom so the image just fits the viewer without any extra space, but also without being cropped at all.

    [04:25 - 04:31] The zoom value to match the width of an image is this. For the height, you need to take into account the aspect ratio.

    [04:32 - 04:40] Between these two, we just want whichever is more zoomed out. That way we always see the full image regardless of whether it's more constrained on the horizontal or vertical axis.

    [04:41 - 04:48] Now that we have all that info, we can use it for a new tween. Note that we're using a linear easing, so theoretically it should zoom at the same speed the entire time.

    [04:49 - 04:57] Give it a try and you'll notice that it actually zooms faster at the beginning and slows down at the end. This is because zoom works on an exponential scale rather than a linear one.

    [04:58 - 05:12] In other words, if you're zooming from one to four, halfway there is two. Tween doesn't have facilities for animating on exponential scales, but we can make it work by converting our start and end values into log space and then converting the resulting value back.

    [05:13 - 05:20] Give that a try and you should see that it's zooming much more evenly all the way through the animation. Now that we're happy with the math, we can switch to a more pleasing easing.

    [05:21 - 05:27] Okay, now it zooms and pans to the correct location. It's still super fast when it pans a long distance from image to image.

    [05:28 - 05:37] We don't want to give our users motion sickness. Fortunately, if we zoom out a bit in the middle, we cut down on the perceived speed a lot, and as a bonus, the user gets a nice overview of where they're headed.

    [05:38 - 05:46] To do this, we'll want to break the zoom tween into two tweens, one that zooms out and another that zooms back in. We need to know where we're zooming out to.

    [05:47 - 05:54] Let's say a fifth of whichever zoom is deepest, the start or end. We don't want to do that zooming out thing if we're starting all the way zoomed out though.

    [05:55 - 06:02] In that case, it's perfectly fine to do it the way we've already been doing it. We can check if our start zoom is less than our mid zoom and use the old technique.

    [06:03 - 06:18] In fact, we don't even really want the zoom out if our start zoom is only a little bit zoomed in from the mid zoom, so we can test it against mid zoom times two or some such. For the new technique, we want to first zoom from the start zoom to the mid zoom, taking half of the time of the full animation.

    [06:19 - 06:26] We'll use the same in-out easing, so we rest for a moment of the fully zoomed out point. Then we do the second tween, which goes from mid zoom to end zoom.

    [06:27 - 06:37] And then we can chain them together so the second tween happens immediately after the first one. Give it a try. It should produce a nice smooth animation from any place to any other place in the viewer.

    [06:38 - 06:50] You've created a nice nuanced animation. Along the way, we've touched on a lot of the concerns you'll need to take into account while animating, such as making sure that you're scaling your zoom correctly so it feels right.

    [06:51 - 07:02] You've also learned how to use tween.js, which can be handy in many situations. In the next module, you'll add overlays to your images to provide your users with extra information and interaction opportunities.

    [07:03 - 07:08] But first, check out the next lesson for some exercises to practice what you've learned so far.