The Magic Behind Shiki Magic Move
Chapters13
Introduces the goal of the video: a deep dive into how Shiki Magic Move works, including how it uses flip animation to orchestrate transitions and what lessons can be learned from the library.
Joy of Code’s deep dive shows exactly how Shiki Magic Move turns code into tokens and animates them with flip tech for smooth, line-aware transitions.
Summary
Joy of Code’s video with Chris (Joy of Code) dives into the inner workings of Shiki Magic Move. He demonstrates the two main parts—core and renderer—and explains how Shiki converts code into tokens, keys them, and diffs previous vs. current versions. The talk walks through key concepts like enter, leave, and move transitions, with a practical example of animating a semicolon token using the flip animation technique. Chris also shares how the renderer maps tokens to DOM elements, schedules transitions, and uses an anchor to stabilize measurements during layout changes. He emphasizes the role of a diffing library (diff-match-patch) to compute token matches and how tokens are re-linked to keep smooth motion. The walkthrough includes debugging steps in VS Code and notes that the real project adds touches like line numbers and spacing, but the essence remains: token-based diffing plus flip-driven motion. A sponsorship mention with Code Crafters is included for viewers who enjoy deeper, project-based learning. Finally, Chris hints at practical caveats, like container resizing and the need to force reflows to ensure transforms animate, and points to the blog and GitHub for full code references.
Key Takeaways
- Shiki Magic Move splits code into tokens, adds unique keys, and diffs from tokens to tokens to determine which pieces moved, were inserted, or were deleted.
- The core exports a create magic move machine that converts code strings into keyed tokens and syncs previous/current states to drive transitions.
- Enter, leave, and move transitions are orchestrated with enter/leave CSS classes and the flip animation technique for position changes.
- The renderer maps tokens to DOM elements, groups them into move/enter/leave categories, and uses an anchor element to measure positions reliably during resizes.
- Move transitions use the flip method by recording first/last positions, applying an inverse transform, forcing a reflow, and then animating to the final state.
- A diff library (diff-match-patch) computes the delta between code versions and produces match ranges that determine which tokens stay, move, or change, enabling granular animation without rebuilding the whole block.
- The practical implementation includes performance tricks like forced reflow and scheduled cleanup promises to manage the timing of transitions and avoid layout thrash.”],
Who Is This For?
Frontend engineers and JS/TypeScript fans who want to understand code-driven UI animations, token-based diffing, and the flip animation approach behind Shiki Magic Move. Essential for creators building editor-like UIs or dynamic code highlights that need smooth, line-aware transitions.
Notable Quotes
"So basically, we're going to record its first position, let's say, at 100 pixels. Then, we're going to record its last position at 200 pixels."
—Explains the core idea behind the flip technique by tracking token positions.
"Shiki magic move is made of two parts, the core and the renderer."
—Defines the architectural split of the library.
"Enter elements fade in. Leave elements fade out and move elements use the flip animation technique to smoothly transition their position."
—Describes the three token lifecycle categories used in the renderer.
"The viewer is going to see a poor example of how the semicolon moves, but that’s the essence of the flip trick in action."
—Illustrates a concrete token example used in the explanation.
"We’re going to diff the code strings with diff-match-patch to find what changed and then map those changes to token ranges."
—Describes how token diffs are computed and used for animation.
Questions This Video Answers
- how does shiki magic move diff tokens to drive animations
- what is the flip animation technique and how is it used in code editors
- how do enter, leave, and move transitions differ in token-based animations
- what role does diff-match-patch play in code token animation
- how can i implement a token-based code highlighter with smooth transitions
Shiki Magic MoveTypeScriptFlip AnimationDiff-Match-PatchCode TokensEnter/Leave/Move TransitionsRendererToken KeyingAnimation OrchestrationCSS Transitions
Full Transcript
Hey friends, in a previous video I showed you how you can smoothly animate call blocks using Shiki magic move in Typescript. And as you can see, I even use Shiki magic move personally inside of my slide library. So here we have this beautiful call block that we can beautifully animate. So you're going to see it's going to smoothly transition. All right. So in this video I actually want to deep dive into how Shiki Magic move works. I know. Imagine learning how cold works in the age of AI. absolutely ridiculous. But even if you don't care about animating cold blocks, there are so many cool lessons in this library.
We're going to learn everything from how Shiki Magic move dips the code to how it orchestrate transitions using the flip animation technique. And if you're the type of person that loves enjoying deep dives into how things work, you might enjoy the message from our sponsor. Recently, I had the opportunity to partner with Code Crafters. If you love learning how things work, then you might love Code Crafters, which is an interactive project-based learning platform for software engineers. So, if you want to support my channel, you can use my referral code in the description, and you can uncover many of the interesting lessons on code crafters like building your own shell, radius, grap or interpreter.
So, you can see they have these great lessons here that you can dive into. So, you can sign up for free to cold crafters and you can use my link to get a discount. Thank you to cold crafters for sponsoring the video. All right, friends. So in the previous video we ended up with something like this where we import the shiki highlighter create to key tokens create magic move machine from the shiki magic move core the magic move renderer from shiki magic move renderer and the styles. So basically we just created this highlighter here using shiki and this class magic move which has the machine and renderer.
So we just create a pre-element here. We create the magic move machine the magic move renderer and then we have this simple update function that calls machinecommit and renderer. render to render the tokens. So inside of this vtab you just target the app element and then we have our steps here. So we create a new magic move machine where you pass the target highlighter and steps. We can specify the language and the team. And now we just toggle the code inside of here. So we can say code update when we click on the page. So we can use steps before or steps after.
And then we get this result. And of course I also enabled some debugging here. But look how beautiful this is and it just works. All right. But in this example, I made a simple version of Shiki Magic Move so we can understand how it works. So you can see we're just importing it directly from Shiki Magic Move here. And I'm also going to include a link to the blog post and the GitHub if you want to look at the code later where everything is commented and explained. All right, so let's take a deep dive into how Shiki Magic Moo works together.
All right, before we look at any code, I think it would be really beneficial to understand how Shiki magic move works at a high level. So basically, we're going to use Shiki to turn a whole string into tokens. So this is basically just an array of objects that describes the syntax. And then we're going to do some transition orchestration using the flip animation technique and CSS transitions. So we're going to have this enter, leave, and move elements. Enter elements fade in. As we can see here, leave elements fade out and move elements use the flip animation technique to smoothly transition their position.
And if you don't know, the flip animation technique is really cool. Today, you would use something like view transitions, but flip animation still range supreme. So, here's how it works. Let's say, for example, that you want to animate the semicolon token. So, we're going to record its first position, let's say, at 100 pixels. Then, we're going to record its last position at 200 pixels. So, here is where we do the illusion by inverting the position. So we're going to use element style transform and then we can use a translate. So we can translate it to its original position and then we can schedule a transition where we can remove the transform to play the transition.
So we're just creating an illusion that the element is moving from its old position. And of course this is how our transition orchestration works. The lip elements fade out their absolutely position. So they're going to be in their original position. And as they're fading out, the enter elements are going to fade in. So this false token here is already in position here. And then move elements are going to transition from their old position using the flip animation technique. And don't worry, we're going to look at the code and explain more how this works later. All right.
So shiki magic move is made of two parts, the core and the renderer. For the core, we have this create magic move machine function, and its main role is to turn co strings into tokens. And then we're going to add keys to them by using call tokey tokens. And then we need to sync the keys between previous and current versions. So we can classify which elements are moved, inserted or deleted. So let's see how this works. So here we have code to key tokens. So we're going to turn this code string into tokens. As you can see, this is just an object with a content offset color and etc.
And then we're going to add the keys to the tokens offset. So where is their position? And we're going to add a new line for each line. So here we have letpool equals true. We're going to add a new line and we're going to add a key to these tokens. So this is the from state and the to state is going to be the same. So we're also going to key the tokens. All right. And then moving forward we have sync token keys. So we're going to diff the co strings where zero means no change, one equals insertion and minus one equals deletion.
So for example, if we div these two co strings let bool true and let bull false then we're going to get this array. So we can see that let bull equals remains the same. True should be removed and false should be inserted and also e semicolon didn't change. So we're going to basically create match ranges for tokens. And this sounds complicated but it's actually very simple. So let's actually look at this. So we're going to see that lead bull didn't really change from the from and to position. It goes from 0 to 11. So we can see that e changed.
It went from 141 16 to 15 and 17. So now that we created these match token ranges, we can actually use this to get the appropriate tokens from our from and to pile. So let me actually show you how this looks like. All right. So we can look at this offset and we can also look at the tokens. So we're going to get the right tokens. So we're going to filter the tokens based on this range. So we're going to get these tokens let bull equals and of course the empty white space. And they're going to be the same between from and two.
And also we're going to get the next token range from from two to where it's e semicolon. But of course E isn't a valid token. So we're only going to get the semicolon. And then we're going to compare the from and to tokens for matches. So if a match, we're going to assign the from token key to two token key. So since we know that these are the same tokens, we're just going to give them the same key. So we know that this token should transition. And the same is true for the semicolon. As you can see, we only filter based on the value token.
So it's not going to be e semicolon. It's going to be just the semicolon based on its range. All right. So let's dive into the code. All right. So I'm going to use the VS code debugger to step through the code and see how it works. So here I already set a break point on the initial render which is going to step into this update function. So let's just rerun this code and it should trip the debugger. All right. So now we can step into our update function. So now we're invoking this machine commit with the call string.
So let's see what happens. So now when we step into it, we have this create magic move machine which was already initialized with some empty key tokens. We're going to look at this function in a second. And then you have this previous and current state. So the create magic move machine returns this commit function as you can see here. So it returns this getter and setter previous and current and the commit function. So inside of the commit function, we're going to always assign to previous the current state. And then we're also going to merge some options if the user passes them.
This is not important. So let me actually just step through this. And here is the interesting part. So we're going to create the new key tokens here using this code to key tokens function where you pass the code the merge options with line numbers if specified. And then when we get the new tokens, we're going to pass them to the sync token keys function which is going to accept the previous and the new tokens with the merge options. And then we're going to assign the destructured values from two to previous and current right here. So this looks like normal destructuring, but this is actually the structured assignment.
As you might have noticed, there is no keyword here. So this line of code is actually pretty cursed, but I didn't write it, so don't blame me. And then of course, when everything is said and done, we're going to return the previous and current tokens. All right, so let's step into the code to key tokens function. All right, so here is the call back where we passed it in our original code. All right, so now we're inside of this function, and you can ignore the types. They're pretty cursed to be honest. So this function just accepts the shiki highlighter code options and line numbers if you pass them.
All right, so here we turn the code string into tokens and we're actually going to skip this line of code because this is going to step into the shiki code source. So you can just skip over it. But now we can actually see the results what our string code became. So we can see these tokens are right here. So this is what let bull equals true became. So we have these tokens with the content offset color font style and etc. All right. So this is the interesting part to us. Now we pass the code and tokens to two key tokens.
And then we're going to spread whatever it returns. All right. So we're going to pass the quote and the result tokens. All right. So this line looks weird but this is just a salt used for hashing. So we have a unique ID for the key. So this is going to base the salt on this options that we passed. And then of course we pass the line numbers and some other properties used for rendering such as the background, foreground, loot style, theme name, and the language. All right, but let's actually step into this function. All right, so now that we step into this function, we can see that we have our code string here, lets equal true.
We have our tokens that we created and we have the salt, which is going to be just the string based on the options that we passed. So now here where we have this first line where we create a unique hash, we can just use this get hash function. And for this we're just using this library for hashing. Let me just see at the top. Yeah, hash from o hash. So we're going to actually skip over this line of code so we don't step into this code because we don't care about this. All right, so let's skip it and we're going to see it creates this hash which is just a long string.
All right, so here's the part where we add the keys to the tokens. So we're going to loop over the tokens. Basically tokens are a two-dimensional array where every array represents a line. But in our case, there is only one line. So as you can see here, we use a flat map to flatten the array. And then we're going to get the less token from the line. So we know the last element because we want to add a new line to the end. All right. So if the element doesn't exist, we're just going to increment for an empty line.
Otherwise, we calculate the position of the last token. So let's actually step through this. Now you can see here we have this flat map and here is a line. So you can see we only have one line which is let bull equals true. Otherwise this would be a two-dimensional array. All right. So now let's step through this. Of course it exists. So now we're going to calculate the last offset. So we can say last element offset plus last element content length. So we can see that last offset was 16. And we can see if we look at our tokens for example we can see 15.
Yeah. So the new character that you're going to add new line is going to be at position 16. So that checks out. All right. So we're going to add a new line to the end with the right offset. And then later the new line is turned into Brazil. All right. So we can see here we have this token. So now we just spread the old line. We add a new line with the appropriate offset. So if I hover over tokens, we're going to see here we have a new line. All right. So now that this is done, we're going to map over them again.
And now we're going to add the keys based on the hash that we created and the index. All right. So now you can actually see where we have our token. The first token is going to be let. And here we have this index of zero. So now we're going to assign it to let. As you can see now we assign this key with this long hash. All right. So now we can actually do this for all of the tokens. All right. So now this is done. And actually let's see if we can look into tokens.
Now we keyed each of these tokens. So they have a unique key from 0 to 7. As you can see, we also added the appropriate offset for the new line character. That's basically the task of this function. All right. So now we can return the code hash and the key tokens. All right. So now this is done. Now we can exit this function. So now we have our new tokens. All right. So to recap, we had to turn the co string into tokens. Then we add a key to each of these tokens. So it's properly sorted as you can see with a new line and all the appropriate offsets.
All right. Now we can pass the result to sync token keys where we div the code and match the keys. All right. But nothing interesting is going to happen when we step into the sync token keys function. And that is because we're comparing it to the from state which is an empty string since this is the initial render. So we're comparing this empty string to our two tokens. So we're not going to get any matches. So for example, if I skip this and I look at matches, there is not going to be anything. So I can skip most of this code here.
So we can trip the debugger on the next update. So if I just go here now, we're going to see we're done with this machine commit part and then we start to get into the renderer, which we're going to talk about in the next part. But what I'm actually going to do here, I'm just going to remove this break point. I'm going to set it here where we have this machine commit. So now this is going to trip when we trigger this code.date method here. And this is going to be more interesting. All right. So let me actually restart this.
And then I'm going to also press play because I actually just want this code to render so we can trip the debugger. All right. So now when we update this code string, it should trip the debugger. Awesome. All right. So let's step into this. Same as before, we assign to previous the current. So now if I step over this, you're going to see that the previous version is let pool equals true. And now you can see the current version is still not updated. So we can just step over this. Let's go into new tokens. So we can go through this again if you want.
But I can just skip this because now we can see we get these tokens based on this lets equals false string. And now this is the interesting part. So now we're passing the previous let equals true tokens with a new tokens. let bull equals false. So this is where the magic happens. All right. So let's step into this. All right. So now we can see that sync token keys accepts the from and two tokens with some options. So now we're going to use a diff library to find the differences and get the match ranges. So let's step into it.
As you can see the fine text matches accepts an A and B argument which are just our code strings. In this example is just let bulls equals true and let bull equals false. So now we need to find the differences between them using this diffing library. And if you're curious, the name of this library is diff from diff match patch. Yes. All right. So we're basically going to run this function. Nothing complicated. Let me actually just step over it because we don't actually want to go into the code of this diffing algorithm. So now we can see we get the delta which is going to be this array that we seen in the illustration before.
So we get what didn't change like let bulls equals and this e semicolon. We know what needs to be deleted like this true string and we know what needs to be added like false here. All right. So this line is just optional if you pass a custom diffing algorithm. So you can actually skip this. All right. So here's the cool part. So we have this A and B columns to calculate the offset ranges for matches. So let's see how this looks like. So here are match ranges and now we can loop over them and do something based on the operation.
So if they match or if they need to be deleted or inserted. All right. So let's see how this looks like. So here we loop over this delta which is an array of array. So we can destructure the operation and the text. All right. Now we can see that the operation is zero and the current text is let bulls equals. All right. So now that this is zero this is a match. So we can just push it with a from and two value. So let's see if we can see it. So from is basically the A column length and the A column length plus text length and two is going to be the B column length with the B column length plus text length and of course the content is going to be the text that we pass which is going to be let bull equals and then you're going to add the text to both the columns.
So basically we're just using these columns to calculate the offset ranges and we're going to see how this looks like. All right so let's actually step over this. So now we're going to see that a content now is let bull equals and the b column is also let bull equals. You can also see it here on the left side. All right. So what is going to be next? Now we have this operation minus one. So deletion and it's going to be true. Real and true. All right. So we can go else here. So now we get the operation here is minus one.
So if it's a deletion, we're going to add it to the a column. All right. So let's do that. Now we add it to the column. You can see here a content is true. All right. So let's continue. Now we're going to have this operation one and the text is going to be false. All right. So again it's now going to be insertion. So we're going to add it to the B column. All right. Let's do that. All right. So now you can see how our A and B column looks like. We have let bull equals true and we have let bull equals false.
All right. So let's go next. Now the operation is going to be zero and the text is going to be e with a semicolon. All right. So now this should be a match and now we can just push it to our matches and let's add it to the columns. All right. So now we can see we have our a column let bull equals true with a semicolon and our b column let bull equals false with a semicolon. And now let's see we have this match ranges here. So now we can see that this range for letpool equals is from 0 to 11.
This is also going to match our token offsets. and it didn't change. So two is the same from 0 to 11. And then of course we have our second match e semicolon. It went from 1416 to position 15 and 17. All right. So now that you have this, we can actually just return the matches. And this might sound confusing, but it's actually really simple. We're just going to use this range to filter the tokens that we need. So we're going to loop over every match. So let me just go here and I can say match.
So if we hover over the match, the first match is going to be let pull equals with is from and two properties. So now we're going to use it to get the from tokens and the two tokens based on their offset. Right? So as you can see here is our filtering logic. So if I step over it, you're going to see now we get the appropriate tokens. Boom. It just filtered the right tokens. Let bull equals and we also have let tokens too. So if I skip over it, you're going to see we're going to get the same tokens because nothing changed, right?
All right. So this is a simple token matching algorithm. It sounds spooky, but it's really simple. So if a token content matches, it's just going to assign them the same ID. All right. So let's actually step through it to understand how it works. All right. So we're just going to loop over them at the same time. So we have our from and two tokens. We're going to skip if there is no token. All right. So if there's a match, we're going to assign the from token key to the two token key. So they're going to be the same.
All right. So we can look at this and now we can just loop over it. Now we can increment it and move on. All right. So now we can just continue here until we do this for all the tokens. You can see we're starting to build them up. And one important thing worth mentioning is this is actually mutating the original token. So basically that's what we're doing here. And there we are. So now we're going to go through the second match which is going to be E semicolon. All right. So let's skip through this and you're going to see we're going to only get the value token.
So for example, this E semicolon thing doesn't exist. But because we have the range here, we can just look at the offset. So the tokens from is just going to return the semicolon here. And same for tokens too. And this is how we're going to know where the semicolon token moved. All right. So now when we do this, we're just going to do the same thing again. All right. So they have the same content. We're going to assign the key. And that's basically it. All right. So we can step here and we can see now when you look at the tokens where you have from now we can see let bull equals it has the same ID as before and if you look at two it also has the same matching ids but you can see it has a different key where it changed.
So here it was true now it's false now it has a new key. So now in the renderer we know which items we need to move, insert or delete. All right, but basically this is it. This is how we seeing the tokens. All right. So now we can go back and now we get previous current here which we're going to return. And again this isn't regular the structuring. We're reassigning the previous and current values here. I told you this line of code is very cursed. This is called assign the structuring. And that's basically it for the shiki magic move core.
All right. So now that we have this information, we can use it in the renderer to classify the tokens. As you can see, if you just step through this and now we go into our renderer. All right, but before you step into the renderer code, let me give you a highlevel overview of how it works. All right, let's talk about the shiki magic move renderer. Basically, we have this magic move renderer class with a render method. And the main role of the renderer is to map the key tokens to DOM elements, classify them as move, enter or leave and then to orchestrate the transitions.
So for example on the initial render, this is what happens. So let me just zoom in a bit. So first we're creating this code block pre with the shiki magic move container class which just contains this pans with the class of shiki magic move item. And we're also going to create an anchor here for the flip animation technique. So we can measure the position relative to the anchor in case the container size changes. And all of these tokens are going to be stored in this mapdome property on the class. All right. So the initial render just renders these tokens to the screen.
But let's see what happens on subsequent renders. All right. In this example, we started with let bull equals true. But now we change it to let bool equals false. So what happens? Well, first we measure the current mapdom element position relative to the anchor. And then we assign the same token keys to the move array and remove them from map DOM. So new elements are assigned to the enter array and invisible and the remaining mapdom elements are assigned to the leave array. So after we update the DOM, the leave elements are placed in their original position.
So as you can see in this example, true is already here at its original position and as it fades out, the new false token is going to fade in. All right. So we can look at how we orchestrate the transitions. So in this example where we have leave transition, we're going to absolutely position it to the old position and then we're going to add a CSA transition and a delay equal to the duration by applying the shiki magic move leave class and then we're going to schedule a fade out transition for later which applies the shiki magic move leave to class.
And then we schedule a cleanup to remove the transitions. And for the enter transition, we start at zero opacity by applying shiki magic move enter from class. And then we add a CSS transition and delay equal to the duration by applying shiki magic move enter active as a class. So we schedule the fade in transition for later which applies shiki magic move enter from class and then we schedule a cleanup to remove the transitions. And for the move transitions we're going to use the flip animation technique. So for every token we're going to get the first position the last position and then we're going to invert the element to the first position.
We're going to apply a transform instantly. And then we're going to schedule the transition, which is going to add the CSS transition, and then remove the transform to play the transition. And then we're going to schedule a cleanup to remove the transition. And what we also have to do is force the layout to apply. If we don't apply the DOM changes instantly by forcing a reflow, then the transform would get ignored because the browser is going to batch the updates. So the last state would be when we remove the transform. So basically in your browser this is how layout works.
So basically you have JavaScript styles layout paint and compositing. So to apply the dome changes instantly we're going to use JavaScript in this step. So where you have style layout JavaScript we're going to use JavaScript to read the dimensions of the document. So this is going to put us back into this step. So the browser doesn't batch the updates. And then we're going to run all of the transitions and clean up after the transitions are done. All right. So all of this is going to make more sense when we look at the code. All right.
So for the renderer, we're going to do the same steps as before where we go first through the initial render and then we're going to look at what happens when we update the code. All right. So I set a break point on this line where we invoke this. And we pass it this.curren which are current tokens. All right. So if we trip the debugger, we're going to see there's nothing in the DOM yet. So let's step into it. And first we're going to step into this getter. So we're going to get our current tokens. And then we can step into the renderer itself.
All right. So this is just a render method on this class called magic move renderer. And I should also mention that we have some other things here. So we have some class prefixes just so we don't have to type them out. So here we have the shiki magic move class and then it deres all of these other classes. And of course if you want there are also shaky styles. So you can look at this if you want. There's nothing special here really. This is really important for the transitions but it's not magic right? just applying the transitions with the delays on them and whatever, right?
So, you can look at this source code if you want. And here we have some default options for the duration, stagger, easing and so on. But anyhow, here we have this magic move renderer class. So, we have this map DOM property which holds the DOM elements to render. Here we have the code container. All right. So, here we have this anchor. So we use it to measure the XY coordinates of the tokens relative to the anchor in case the container size changes and we also don't want to run any transitions on the first render. You're going to see where this comes into play.
And here we have some options and that's it. All right. So here in the constructor we pass the target where we're going to attach the code element to and the magic move render options. All right. So here we just merge the options and then we assign the container to the target. And here is where we create the top left anchor. So we create this span. We position it absolutely and it's just this tiny thing of one pixel in width and height and then we say this container.prepend this dot anchor. All right, but let's actually talk about the initial render.
So first we have this render function here. We're going to set some CSS variables on the container. So if we step into this, we can see that we have a bunch of CSS variables for duration, delay, easing, and so on. All right, but we can just actually step through them since they're not that All right. So, here we create a new map of the DOM. And here we have our move tokens, our enter tokens, and leave tokens. But of course, this is the initial render, so it's not going to be that exciting. And then we also keep track of our promises for cleanup, which we're going to look into later.
And this is for cancelling all transitions. And this is also more important. So, this is where we're going to push our schedule transitions. And then when we apply the DOM layout changes, we're going to run the callbacks. All right. So, this is more important. when we update the code, we're going to track the position. So, we're going to skip this also because nothing is happening. If we look at map DOM right now, it's empty. So, this is useless basically to us. All right. So, this is the interesting part. So, here is where you create the DOM elements from the tokens.
So, we're going to create new children by stepping over the tokens. All right. So, if we step into this, we can see that for example, the first token that we have is going to be let and we're going to see, hey, does this already exist? It doesn't. So, we're going to create a new element which is going to be a span. Unless it's Brazil, then we're going to create Brazil. And as you can see, I also have some debugging here using this outline, but it's really not important. All right. So, here's what happens. So, we create this new element and then we're going to apply some element styles.
So, we're going to say this apply element. We're going to pass the newly created element with a token which has all of the information for how it should look. All right. So, if we step into this here, we have apply element. We're going to apply the element content and the styles. All right. So if we loop over it, we can see if it's not a new line, then we're going to assign the token content to the text content of the element. And then we're going to add any classes. All right. So now we're going to apply the element styles.
So now we can see if token color, then the element style color is going to equal the token color. And it's really simple as that. It's a bit more complicated in the original since it has a bunch of more properties, but that's basically it. All right. And now we're going to push the new element to the enter array. So here is our enter elements right above here. And that's basically it. All right. And then we're going to update the new DOM map. So if you look at the new DOM map, you're going to see here is our map where we have the ID and we have the element itself.
All right. So now we can just actually quickly go over this elements. It's just going to be the same thing, right? All right. So now that it's done, we can actually see that we have this new DOM map here. Where is it? All right. So this is all of our keys and the elements. All right. So you can see from zero to 8 and we created the DOM elements from them. All right. So let's see what happens next. We can skip this part actually because we don't have any elements to delete. You're going to see that when we update the element, this also isn't important.
All right. So now we're going to see that our tokens are going to render. We're going to update the DOM by saying this.container.replace children. We're going to pass the anchor, the new children, and any leave elements. All right. So, if you step over it, you're going to see boom. And as you can see now, we render the elements in the browser. And nothing exciting actually happens here because we don't have anything to compare it to. As you can see here, we're checking if this is the first render because these are enter elements. We actually don't want to run any transitions.
So, you can actually just skip over this until we update the code for later. And let me step over this. And now we're going to set this first render to false. And we can just return this promise. And this returns a promise in case you want to run some callbacks before and after the transitions are done. All right. So that's basically it. So let me actually restart this. And now I'm going to show you what happens when we update the code. All right, friends. We're going to do the same thing as before, but this time we're going to skip the initial render.
So let's press play. And now we render these tokens in the browser. And now we can click on this document. So this is going to update the code and trigger the debugger. All right. So now we're going to get the current tokens again. So let's step into the render method. So this time we're actually going to skip setting the CSS variables. And now we're going to create a new DOM map. Same as before. So here you have our move, enter, leave elements. We can skip this for now until the right time comes. All right. So here is the relevant part here.
So now we're going to record the current element position because if we hover over mapdom elements now it's no longer empty. It has the old tokens from before. All right. So we're going to create a new map position which holds the X and Y coordinates for every element. And we also need to make it relative to the anchor. So we're going to get its position using get bounding client rect. All right. So now we can loop over all of our tokens. So for example the first element is going to be let. So now we can get its position and then we can set a position.
So you can say position set. We can pass the element and we can subtract the anchor x and y from the element x and y to get the relative position. All right. So now you can just quickly move through this. All right. And now if you look at the position, we have the position of all of our tokens. So how cool is that friends? We have this value here x and y. So we can use this later when we use the flip animation technique. All right. So this is going to be different this time where we create the DOM elements from tokens because now we actually have elements in the DOM.
All right. So we're checking hey if this map DOM has this current key. So let's step into it. We're going to see that the key is going to be this string right. We're going to get it from map DOM and then we're going to apply the element content. So same as before we already seen this and now we actually have to schedule the element style transition. And this is because later when we use the flip animation technique, we're going to assign a class that's going to let the element transition. So if we have a color or something, it's not going to transition if you apply it right now.
So we're going to schedule the transition. And now we can add this element to the move array by saying move.push and pass the element. And we're going to set it to the new DOM map using the token key and the element. And we're going to remove it from this old map. All right. So, we're going to return the element. And now we can just skip this for all of the elements. All right. So, we step through all of this code here. And now we're at this section. So, anything left in the DOM map should leave.
So, let me actually show you why we deleted this from the old DOM map because now this is going to be our leave elements. So, for example, if we look at the map DOM, we're going to see we have this token and Brazil here. So now when we step through this, we're going to see, hey, if the element is Brazil, then we're going to ignore it. Otherwise, we're going to push this to the leave array. That's how simple that is. And now this is important. Now the leave elements stay in the DOM so we can fade them out.
And they have to be positioned absolutely. So now we're going to update the DOM, which is going to happen instantly. And we're going to update this map DOM with the new DOM map. And you can see the weird state we're in here because we now have to orchestrate the transition. So let's handle the leave elements. So this is how we're going to do it. We need to make sure that their position absolutely. And now we're going to set them to the previous position. So we're going to get their position. And you can say element style top their old Y position and element style left with their old X position.
And now we can see it looks a bit better. All right. So we're going to add a class with a transition. And now we're actually going to orchestrate the transition by scheduling it for later. So we want to set the opacity to zero of the leave element. As you can see now it's overlaid on top of our true element. All right. So let's step through this. And now we're going to have a cleanup later. So we're going to say promises push. So we're going to invoke this function register transition end which accepts an element and the cleanup function that you want to do.
Let me actually show you this. It's really not that intimidating. It looks confusing at first but it's really just simple. So this basically just returns a promise, right? So we have this function which has resolved set to false and we have this resolve function which we're going to set. All right. So here we have this promise which is just erased so we can cancel this transition later. So the only thing that this does is tells you when all of the animations are finished. So it says promise dot all settled element get animations. You're going to map over them.
If they're finished then you're going to invoke this call back. And we have a promise here if we want to cancel it. if we for example render it immediately. So what this does is this updates this resolve function here to say if resolve return. We're going to set resolve to true invoke our callback and we're going to resolve the promise and then we just say promise.resolve is equal to resolve and then we return the promise. So now we can easily cancel this if we want. All right. So let's actually go through the next section. So here where we check if this is the first render now it's no longer true.
So now let's see how we handle the enter transitions. All right. So for every element we're going to start at zero opacity by applying this class and then we're going to also add this class for the transition and delay. And then we schedule the transition. So we want to animate the opacity. So as the leave element fades out the enter element should fade in. And then again we have this cleanup but we can skip it since we now understand how it works. All right. So we can do this for all the elements. And now let's actually look how we handle move elements.
So this is where we use the flip animation technique. All right. So let's look at how this works. All right. So first we get the initial position. Then we get the last position using get bounding client rect. And then the position is relative to the anchor if the container size changes. So we have to take that into account. And then here is the illusion that we're creating. We're going to invert the position and set it to its initial position by specifying a dx and dy constants. And then we just need to apply the transform using those values.
And we can also set the duration to 0 milliseconds so it happens instantly. All right. So here is where we schedule the transition. So first we're going to add this class with a transition move. And then we can play it by removing the transform. We're also going to clear the transition duration and the transition delay. And of course then we schedule the cleanup. So we can skip this. And this is the most important part. So let me actually just skip over all of these things. So this is going to be all of our elements. All right.
So here's what's going to happen. Now that we looped over each of these elements and we change their position. Now we have to apply the dome layout changes. Otherwise this transform here would just be ignored. Let me actually see it where it is. Yes, this would be just ignored because the browser would batch the updates and then we would just end up with an empty transform. So nothing would happen. We have to say to the browser update these changes right now. So this is what we're going to do and this is an interesting trick. So if we go into force reflow by using JavaScript and measuring the body height, we can force a reflow.
So that is basically what we do. We just invoke document.body.offset height. All right. So now that we apply the DOM layout changes, we can run the schedule transitions. Now we're going to see if I just skip over this. You're going to see our transition works like magic. All right. And now we can run the cleanup. So we can just skip over it because it's not really interesting. And we can even store the promises for manual cleanup if another render is triggered. All right, so we already been for this and now we can just return a promise.
And that's basically it, friends. Of course, the original shiki magic move implementation is a bit more involved. So for example, they also split the whites space tokens here. So for example, if you have a token like let and it has a whites space, it breaks it into more granular token. So it's going to create a let token and one with an empty space. But as you can see, the code is pretty much identical. And of course, it also takes care of line numbers. So it has this section if you have line numbers. You can study how this works if you're interested.
Let me see if there's anything else here. Well, it's not really that much different. So it just does a bunch of more things here. And of course, the shiki magic move renderer has more features like resizing the container and so on. So let me actually show this to you if I can find it quickly. So here we set the CSS variables. It's not really important anymore. All right. So here we have render. We can actually see it does more here. So you can see here it also can set a stagger on the elements if you want.
So you get more beautiful transitions. And then at some point I think it's for the flip animation technique here where we have move but it's actually not here. Let me see. Okay, it's after this move. All right. So it also animates the container size. So, if you try to animate right now multi-line code, it's going to look a bit janky because you actually need this code here that's also animating the container. But yeah, that's basically it. This is the magic of shiki magic move. All right, so let me know if you enjoy this type of video.
Thank you for watching and I'll catch you in the next one. Peace.
More from Joy of Code
Get daily recaps from
Joy of Code
AI-powered summaries delivered to your inbox. Save hours every week while staying fully informed.









