Skip to main content

Code

Press play to preview the animation
                                
                                  
                                    import
                                    
                                    ...
                                    
                                    

export default makeScene2D ( function * ( view ) {
const code = createRef < Code > ( ) ;

view . add (
< Code
ref = { code }
fontSize = { 28 }
fontFamily = { 'JetBrains Mono, monospace' }
offsetX = { - 1 }
x = { - 400 }
code = { 'const number = 7;' }
/> ,
) ;

yield * waitFor ( 0.6 ) ;
yield * all (
code ( ) . code . replace ( code ( ) . findFirstRange ( 'number' ) , 'variable' , 0.6 ) ,
code ( ) . code . prepend ( 0.6 ) ` function example() {\n ` ,
code ( ) . code . append ( 0.6 ) ` \n} ` ,
) ;

yield * waitFor ( 0.6 ) ;
yield * code ( ) . selection ( code ( ) . findFirstRange ( 'variable' ) , 0.6 ) ;

yield * waitFor ( 0.6 ) ;
yield * all (
code ( ) . code ( 'const number = 7;' , 0.6 ) ,
code ( ) . selection ( DEFAULT , 0.6 ) ,
) ;
} ) ;

The Code node is used for displaying code snippets.It supports syntax highlighting and a handful of different methods for animatingthe code.

Parsing and Highlighting

First things first, if you just copy any of the snippets in this tutorial you'llnotice that the displayed code has a uniform color. The default highlighter uses Lezer to parse and highlight the code but todo that it needs the grammar for the language you're using. You can set that upin your project configuration file.

First, install the grammar for your language. You can check out thisnon-exhaustive list of available grammars . For this tutorial,you should install the javascript grammar:

                            
                              
                                npm
                                 i @lezer/javascript
                                

Then, in your project configuration, instantiate a new LezerHighlighter usingthe imported grammar, and set it as the default highlighter:

src/project.ts
                            
                              
                                import
                                
                                {
                                makeProject
                                }
                                
                                from
                                
                                '@motion-canvas/core'
                                ;
                                
                                
import example from './scenes/example?scene' ;

import { Code , LezerHighlighter } from '@motion-canvas/2d' ;
import { parser } from '@lezer/javascript' ;

Code . defaultHighlighter = new LezerHighlighter ( parser ) ;

export default makeProject ( {
scenes : [ example ] ,
} ) ;

Now all Code nodes in your project will use @lezer/javascript to parse andhighlight the snippets. If you want to use more than one language, check out the Multiple Languages section.

info

Note that, by default, the JavaScript parser doesn't support JSX or TypeScript.You can enable support for these via dialects . The dialectsavailable for a given parser are usually listed in the documentation of thegrammar package.

                                
                                  
                                    Code
                                    .
                                    defaultHighlighter 
                                    =
                                    
                                    new
                                    
                                    LezerHighlighter
                                    (
                                    
                                    
parser . configure ( {
// Provide a space-separated list of dialects to enable:
dialect : 'jsx ts' ,
} ) ,
) ;

Defining Code

The code to display is set via the code property. In the simplest case, you can just use a string:

Press play to preview the animation
                                
                                  
                                    import
                                    
                                    ...
                                    
                                    

export default makeScene2D ( function * ( view ) {
view . add (
< Code
fontSize = { 28 }
code = { 'const number = 7;' }
/> ,
) ;
} ) ;

However, usually code snippets contain multiple lines of code. It's much moreconvenient to use a template string for this (denoted using the backtickcharacter ` ):

                            
                              
                                view
                                .
                                add
                                (
                                
                                
< Code
fontSize = { 28 }
code = { ` \
function example() {
const number = 7;
}
` }
/> ,
) ;

Notice two things here:

  • The code snippet ignores the indentation of the template string itself. Thetemplate string preserves all whitespace characters, so any additional spacesor tabs at the beginning of each line would be included in the snippet.

  • The backslash character ( \ ) at the very beginning is used to escape thefirst newline character. This lets the snippet start on a new line withoutactually including an empty line at the beginning. Without the slash, theequivalent code would have to be written as:

                                    
                                      
                                        view
                                        .
                                        add
                                        (
                                        
                                        
    < Code
    fontSize = { 28 }
    code = { ` function example() {
    const number = 7;
    }
    ` }
    /> ,
    ) ;

Template strings allow you to easily include variables in your code snippetswith the ${} syntax. In the example below, ${name} is replaced with thevalue of the name variable (which is number in this case):

                            
                              
                                const
                                 name 
                                =
                                
                                'number'
                                ;
                                
                                

view . add (
< Code
fontSize = { 28 }
code = { ` \
function example() {
const ${ name } = 7;
}
` }
/> ,
) ;

Any valid JavaScript expression inside the ${} syntax will be included in thecode snippet:

                            
                              
                                const
                                 isRed 
                                =
                                
                                true
                                ;
                                
                                

view . add (
< Code
fontSize = { 28 }
code = { ` \
function example() {
const color = ' ${ isRed ? 'red' : 'blue' } ';
}
` }
/> ,
) ;

Using Signals

If you try to use signals inside the ${} syntax, you'll notice that they don'twork as expected. Invoking a signal inside a template string uses its currentvalue and then never updates the snippet again, even if the signal changes:

Press play to preview the animation
                                
                                  
                                    import
                                    
                                    {
                                    makeScene2D
                                    ,
                                    
                                    Code
                                    }
                                    
                                    from
                                    
                                    '@motion-canvas/2d'
                                    ;
                                    
                                    
import { waitFor } from '@motion-canvas/core' ;

export default makeScene2D ( function * ( view ) {
const nameSignal = Code . createSignal ( 'number' ) ;
view . add (
< Code
fontSize = { 28 }
code = { ` const ${ nameSignal ( ) } = 7; ` }
/> ,
) ;

yield * waitFor ( 1 ) ;
nameSignal ( 'newValue' ) ;
// The code snippet still displays "number" instead of "newValue".
yield * waitFor ( 1 ) ;
} ) ;

Trying to pass the signal without invoking it is even worse. Since each signalis a function, it will be stringified and included in the snippet:

Press play to preview the animation
                                
                                  
                                    import
                                    
                                    ...
                                    
                                    

export default makeScene2D ( function * ( view ) {
const nameSignal = Code . createSignal ( 'number' ) ;
view . add (
< Code
fontSize = { 28 }
code = { ` const ${ nameSignal } = 7; ` }
/> ,
) ;

yield * waitFor ( 1 ) ;
nameSignal ( 'newValue' ) ;
yield * waitFor ( 1 ) ;
} ) ;

This happens because template strings are parsed immediately when our code isexecuted. To work around this, you can use a custom tag function called CODE . It allows the Code node to parse thetemplate string in a custom way and correctly support signals. It's really easyto use, simply put the CODE tag function before your template string:

Press play to preview the animation
                                
                                  
                                    import
                                    
                                    ...
                                    
                                    

export default makeScene2D ( function * ( view ) {
const nameSignal = Code . createSignal ( 'number' ) ;
view . add (
< Code
fontSize = { 28 }
// Note the CODE tag function here:
code = { CODE ` const ${ nameSignal } = 7; ` }
/> ,
) ;

yield * waitFor ( 1 ) ;
nameSignal ( 'newValue' ) ;
// Now the code snippet is updated accordingly.
yield * waitFor ( 1 ) ;
} ) ;

The value returned by CODE can itself be nested in other template strings:

                            
                              
                                const
                                 implementation 
                                =
                                
                                CODE
                                `
                                \
                                
console.log('Hello!');
return 7; ` ;

const method = CODE ` \
greet() {
${ implementation }
} ` ;

const klass = CODE ` \
class Example {
${ method }
}
` ;

view . add ( < Code code = { klass } /> ) ;
// class Example {
// greet() {
// console.log('Hello!');
// return 7;
// }
// }

You might have noticed that these examples used a specialized type of signalcreated using Code.createSignal() .While the generic createSignal() would workfine in these simple examples, the specialized signal will shine once you startanimating your code snippets.

Animating Code

The Code node comes with a few different techniques for animating the codedepending on the level of control you need.

Diffing

The default method for animating code is diffing. It's used whenever you tweenthe code property:

Press play to preview the animation
                                
                                  
                                    import
                                    
                                    ...
                                    
                                    

export default makeScene2D ( function * ( view ) {
const code = createRef < Code > ( ) ;

view . add (
< Code
ref = { code }
fontSize = { 28 }
offsetX = { - 1 }
x = { - 400 }
code = { ` \
function example() {
const number = 9;
} ` }
/> ,
) ;

yield * code ( ) . code ( 'const nine = 9;' , 0.6 ) . wait ( 0.6 ) . back ( 0.6 ) . wait ( 0.6 ) ;
} ) ;

This method uses the patience diff algorithm to determine the differencesbetween the old and new code snippets. It then animates the changes accordingly.

append and prepend

For cases where you want to add some code at the beginning or end of thesnippet, you can use the append and prepend methods. They can eithermodify the code immediately or animate the changes over time:

Press play to preview the animation
                                
                                  
                                    import
                                    
                                    ...
                                    
                                    

export default makeScene2D ( function * ( view ) {
const code = createRef < Code > ( ) ;

view . add (
< Code
ref = { code }
fontSize = { 28 }
offsetX = { - 1 }
x = { - 400 }
/> ,
) ;

// append immediately
code ( ) . code . append ( ` const one = 1; ` ) ;

// animate using the signal signature
yield * code ( ) . code . append ( '\nconst two = 2;' , 0.6 ) ;

// animate using the template tag signature
yield * code ( ) . code . append ( 0.6 ) `
const three = 3; ` ;

// prepend works analogically
yield * code ( ) . code . prepend ( '// example\n' , 0.6 ) ;

yield * waitFor ( 0.6 ) ;
} ) ;

insert , replace , and remove

For more granular control over the changes, you can use insert , replace , and remove to modify the code at specificpoints. Check out Code Ranges for more information on how tospecify points in your code snippets.

Press play to preview the animation
                                
                                  
                                    import
                                    
                                    ...
                                    
                                    

export default makeScene2D ( function * ( view ) {
const code = createRef < Code > ( ) ;

view . add (
< Code
ref = { code }
fontSize = { 28 }
offsetX = { - 1 }
x = { - 400 }
code = { ` \
function example() {
console.log('Hello!');
} ` }
/> ,
) ;

// insert code at line 2, column 0
yield * code ( ) . code . insert ( [ 2 , 0 ] , ' return 7;\n' , 0.6 ) ;

// replace the word "Hello!" with "Goodbye!"
yield * code ( ) . code . replace ( word ( 1 , 15 , 6 ) , 'Goodbye!' , 0.6 ) ;

// remove line 2
yield * code ( ) . code . remove ( lines ( 2 ) , 0.6 ) ;

// animate multiple changes at the same time
yield * all (
code ( ) . code . replace ( word ( 0 , 9 , 7 ) , 'greet' , 0.6 ) ,
code ( ) . code . replace ( word ( 1 , 15 , 8 ) , 'Hello!' , 0.6 ) ,
) ;

yield * waitFor ( 0.6 ) ;
} ) ;

edit

The edit method offers a different wayof defining code transitions. It's used together with the replace , insert , and remove helper functions that are inserted into thetemplate string. They let you specify the changes in a more visual way, withouthaving to know the exact positions in the code:

Press play to preview the animation
                                
                                  
                                    import
                                    
                                    ...
                                    
                                    

export default makeScene2D ( function * ( view ) {
const code = createRef < Code > ( ) ;

view . add (
< Code
ref = { code }
fontSize = { 28 }
offsetX = { - 1 }
x = { - 400 }
/> ,
) ;

yield * code ( ) . code . edit ( 0.6 ) ` \
function example() {
${ insert ( ` // This is a comment
` ) } console.log(" ${ replace ( 'Hello!' , 'Goodbye!' ) } ");
${ remove ( ` return 7;
` ) } } ` ;

yield * waitFor ( 0.6 ) ;
} ) ;

Signals

Notice that all the methods used above are not invoked on the Code node butrather on its code property. It may seem unnecessarily verbose but there's agood reason for it: the code property is a specialized code signal, just likethe ones created by Code.createSignal() . This means thatall the animation methods are also available on your own signals:

Press play to preview the animation
                                
                                  
                                    import
                                    
                                    ...
                                    
                                    

export default makeScene2D ( function * ( view ) {
const message = Code . createSignal ( ` Hello, world! ` ) ;
const body = Code . createSignal ( CODE ` console.log(' ${ message } '); ` ) ;

view . add (
< Code
fontSize = { 28 }
offsetX = { - 1 }
x = { - 400 }
code = { CODE ` \
function hello() {
${ body }
} ` }
/> ,
) ;

yield * waitFor ( 0.3 ) ;
yield * all (
message ( 'Goodbye, world!' , 0.6 ) ,
body . append ( 0.6 ) ` \n return 7; ` ,
) ;
yield * waitFor ( 0.3 ) ;
} ) ;

Code signals can also be nested in the template strings passed to the animationmethods:

Press play to preview the animation
                                
                                  
                                    import
                                    
                                    {
                                    makeScene2D
                                    ,
                                    
                                    Code
                                    ,
                                    
                                    CODE
                                    }
                                    
                                    from
                                    
                                    '@motion-canvas/2d'
                                    ;
                                    
                                    
import { createRef , waitFor } from '@motion-canvas/core' ;

export default makeScene2D ( function * ( view ) {
const code = createRef < Code > ( ) ;

view . add (
< Code
ref = { code }
fontSize = { 28 }
offsetX = { - 1 }
x = { - 400 }
code = { '// example' }
/> ,
) ;

const body = Code . createSignal ( CODE ` console.log('Hello!'); ` ) ;
yield * waitFor ( 0.3 ) ;
// Notice how the CODE tag is not used here because
// "append" already supports nested signals:
yield * code ( ) . code . append ( 0.6 ) `
function hello() {
${ body }
} ` ;

// The "body" signal remains reactive after the append animation:
yield * body ( ` console.log('Goodbye!'); ` , 0.6 ) ;
yield * waitFor ( 0.3 ) ;
} ) ;

Code Ranges

A CodeRange is used to specify a continuous span ofcharacters using line and column numbers. It can be used for editing the code,visually selecting a part of it, or querying the positions and sizes ofcharacters.

Code ranges have the following structure:

                            
                              
                                [
                                [
                                startLine
                                ,
                                 startColumn
                                ]
                                ,
                                
                                [
                                endLine
                                ,
                                 endColumn
                                ]
                                ]
                                ;
                                

For example, to select the first three characters of the second line, you woulduse the following range:

                            
                              
                                [
                                [
                                1
                                ,
                                
                                0
                                ]
                                ,
                                
                                [
                                1
                                ,
                                
                                3
                                ]
                                ]
                                ;
                                

Keep in mind that both lines and columns are zero-based. Additionally, youshould think of columns as being located on the left side of the characters,meaning that if you want to include the character at column n you should use n + 1 as the end column.

For convenience, the word and lines helper functions are provided to create some ofthe common types of ranges:

                            
                              
                                // a range starting at line 1, column 3,
                                
                                
// spanning 3 characters:
word ( 1 , 3 , 3 ) ;

// a range starting at line 1, column 3,
// spanning until the end of the line:
word ( 1 , 3 ) ;

// a range containing lines from 1 to 3 (inclusive):
lines ( 1 , 3 ) ;

// a range containing line 2
lines ( 2 ) ;

Once you create a Code node, you can use its findFirstRange , findAllRanges , and findLastRange methods to find theranges that contain a specific string or match the given regular expression:

Press play to preview the animation
                                
                                  
                                    import
                                    
                                    {
                                    makeScene2D
                                    ,
                                    
                                    Code
                                    }
                                    
                                    from
                                    
                                    '@motion-canvas/2d'
                                    ;
                                    
                                    
import { createRef , waitFor } from '@motion-canvas/core' ;

export default makeScene2D ( function * ( view ) {
const code = createRef < Code > ( ) ;

view . add (
< Code
ref = { code }
fontSize = { 28 }
offsetX = { - 1 }
x = { - 400 }
code = { ` \
function example() {
console.log('Hello!');
} ` }
/> ,
) ;

yield * code ( ) . code . replace (
// find the range of "example" and replace it with "greet"
code ( ) . findFirstRange ( 'example' ) ,
'greet' ,
0.6 ,
) ;

yield * waitFor ( 0.6 ) ;
} ) ;

Code Selection

The selection property can be usedto visually distinguish a part of the code snippet. The selection is specifiedusing an individual code range or an array of ranges:

Press play to preview the animation
                                
                                  
                                    import
                                    
                                    ...
                                    
                                    

export default makeScene2D ( function * ( view ) {
const code = createRef < Code > ( ) ;

view . add (
< Code
ref = { code }
fontSize = { 28 }
offsetX = { - 1 }
x = { - 400 }
code = { ` \
function hello() {
console.log('Hello');
} ` }
/> ,
) ;

// select all instances of "hello" (case-insensitive)
yield * code ( ) . selection ( code ( ) . findAllRanges ( / hello / gi ) , 0.6 ) ;
yield * waitFor ( 0.3 ) ;

// select line 1
yield * code ( ) . selection ( lines ( 1 ) , 0.6 ) ;
yield * waitFor ( 0.3 ) ;

// reset the selection
yield * code ( ) . selection ( DEFAULT , 0.6 ) ;
yield * waitFor ( 0.3 ) ;
} ) ;

Querying Positions and Sizes

getPointBBox and getSelectionBBox can be used toretrieve the position and size of a specific character or a range of characters,respectively. The returned value is a bounding box inthe local space of the Code node.

The following example uses getSelectionBBox to draw a rectangle around theword log :

Press play to preview the animation
                                
                                  
                                    import
                                    
                                    ...
                                    
                                    

export default makeScene2D ( function * ( view ) {
const code = createRef < Code > ( ) ;

view . add (
< Code
ref = { code }
fontSize = { 28 }
offsetX = { - 1 }
x = { - 400 }
code = { ` \
function hello() {
console.log('Hello');
} ` }
/> ,
) ;

const range = createSignal ( ( ) => {
const range = code ( ) . findFirstRange ( 'log' ) ;
const bboxes = code ( ) . getSelectionBBox ( range ) ;
// "getSelectionBBox" returns an array of bboxes,
// one for each line in the range. You can just
// use the first one for this example.
const first = bboxes [ 0 ] ;
return first . expand ( [ 4 , 8 ] ) ;
} ) ;

code ( ) . add (
< Rect
offset = { - 1 }
position = { range ( ) . position }
size = { range ( ) . size }
lineWidth = { 4 }
stroke = { 'white' }
radius = { 8 }
/> ,
) ;
} ) ;

Custom Themes

LezerHighlighter uses CodeMirror's HighlightStyle to assign colors tospecific code tokens. By default, the DefaultHighlightStyle is used. You canspecify your own style by passing it as the second argument to the LezerHighlighter constructor:

                            
                              
                                import
                                
                                {
                                Code
                                ,
                                 LezerHighlighter
                                }
                                
                                from
                                
                                '@motion-canvas/2d'
                                ;
                                
                                
import { HighlightStyle } from '@codemirror/language' ;
import { tags } from '@lezer/highlight' ;
import { parser } from '@lezer/javascript' ;

const MyStyle = HighlightStyle . define ( [
{ tag : tags . keyword , color : 'red' } ,
{ tag : tags . function ( tags . variableName ) , color : 'yellow' } ,
{ tag : tags . number , color : 'blue' } ,
{ tag : tags . string , color : 'green' } ,
// ...
] ) ;

Code . defaultHighlighter = new LezerHighlighter ( parser , MyStyle ) ;

Multiple Languages

You can configure highlighters on a per-node basis using the highlighter property. This willoverride the default highlighter set in the project configuration file:

                            
                              
                                import
                                
                                {
                                Code
                                ,
                                
                                LezerHighlighter
                                }
                                
                                from
                                
                                '@motion-canvas/2d'
                                ;
                                
                                
import { parser } from '@lezer/rust' ;

const RustHighlighter = new LezerHighlighter ( parser ) ;

// ...

view . add (
< Code
// this node uses the default parser
offsetX = { - 1 }
x = { - 400 }
code = { `
function hello() {
console.log('Hello!');
}
` }
/> ,
) ;

view . add (
< Code
// this node uses the Rust parser
highlighter = { RustHighlighter }
offsetX = { 1 }
x = { 400 }
code = { `
fn hello() {
println!("Hello!");
}
` }
/> ,
) ;

It can be useful to create a custom component for the languages you often use.You can use the withDefaults helper function toquickly extend any node with your own defaults:

src/nodes/RustCode.ts
                            
                              
                                import
                                
                                {
                                Code
                                ,
                                 LezerHighlighter
                                ,
                                 withDefaults
                                }
                                
                                from
                                
                                '@motion-canvas/2d'
                                ;
                                
                                
import { parser } from '@lezer/rust' ;

const RustHighlighter = new LezerHighlighter ( parser ) ;

export const RustCode = withDefaults ( Code , {
highlighter : RustHighlighter ,
} ) ;
src/scenes/example.tsx
                            
                              
                                import
                                
                                {
                                RustCode
                                }
                                
                                from
                                
                                '../nodes/RustCode'
                                ;
                                
                                

// ...

view . add (
< RustCode
code = { `
fn hello() {
println!("Hello!");
}
` }
/> ,
) ;

Rendering

If you're using a Chromium-based browser, you might notice that the codeanimations jitter a bit. This happens because the canvas snaps the rendered textto the nearest pixel which prevents it from moving smoothly. Until we find a wayto fix this, we recommend you use Firefox for rendering.