Best practices for @remotion/layout-utils
Take note of the following points to ensure correct measurements when using the @remotion/layout-utils
package.
These tips apply to all of measureText()
, fitText()
, and fillTextBox()
.
Wait until the font is loaded
Only call measureText()
after the font is loaded. This applies to Google Fonts (example below) or any other font loading mechanism.
Example with useEffect
MyComp.tsxtsx
import {useState ,useEffect } from 'react';import {Dimensions ,measureText } from '@remotion/layout-utils';import {loadFont ,fontFamily } from '@remotion/google-fonts/Inter';const {waitUntilDone } =loadFont ('normal');constMyComp :React .FC = () => {const [dimensions ,setDimensions ] =useState <Dimensions | null>(null);useEffect (() => {// Wait until the font is loaded before measuring textwaitUntilDone ().then (() => {constmeasurement =measureText ({fontFamily :fontFamily ,fontSize : 14,fontWeight : '400',text : 'Hello world',});// We don't need to use delayRender() here, because// font loading from @remotion/google-fonts is already wrapped in itsetDimensions (measurement );});}, []);return null;};
MyComp.tsxtsx
import {useState ,useEffect } from 'react';import {Dimensions ,measureText } from '@remotion/layout-utils';import {loadFont ,fontFamily } from '@remotion/google-fonts/Inter';const {waitUntilDone } =loadFont ('normal');constMyComp :React .FC = () => {const [dimensions ,setDimensions ] =useState <Dimensions | null>(null);useEffect (() => {// Wait until the font is loaded before measuring textwaitUntilDone ().then (() => {constmeasurement =measureText ({fontFamily :fontFamily ,fontSize : 14,fontWeight : '400',text : 'Hello world',});// We don't need to use delayRender() here, because// font loading from @remotion/google-fonts is already wrapped in itsetDimensions (measurement );});}, []);return null;};
Example with high-order component
This following logic is borrowed from the Remotion Recorder.
It keeps the code clean by exposing a high-order component which only mounts its children when the font is loaded.
A file is defined which loads some fonts:
fonts.tstsx
import {fontFamily asregularFont ,loadFont asloadRegular ,} from '@remotion/google-fonts/Inter';import {fontFamily asmonospaceFont ,loadFont asloadMonospace ,} from '@remotion/google-fonts/RobotoMono';import {cancelRender ,continueRender ,delayRender } from 'remotion';constregular =loadRegular ();constmonospace =loadMonospace ();export constwaitForFonts = async () => {awaitregular .waitUntilDone ();awaitmonospace .waitUntilDone ();};constdelay =delayRender ('Loading fonts');waitForFonts ().then (() =>continueRender (delay )).catch ((err ) =>cancelRender (err ));
fonts.tstsx
import {fontFamily asregularFont ,loadFont asloadRegular ,} from '@remotion/google-fonts/Inter';import {fontFamily asmonospaceFont ,loadFont asloadMonospace ,} from '@remotion/google-fonts/RobotoMono';import {cancelRender ,continueRender ,delayRender } from 'remotion';constregular =loadRegular ();constmonospace =loadMonospace ();export constwaitForFonts = async () => {awaitregular .waitUntilDone ();awaitmonospace .waitUntilDone ();};constdelay =delayRender ('Loading fonts');waitForFonts ().then (() =>continueRender (delay )).catch ((err ) =>cancelRender (err ));
Then a higher order component is defined which only renders it's children when the font is loaded:
WaitForFonts.tsxtsx
importReact , {useEffect ,useState } from 'react';import {cancelRender ,continueRender ,delayRender } from 'remotion';import {waitForFonts } from './fonts';export constWaitForFonts :React .FC <{children :React .ReactNode ;}> = ({children }) => {const [fontsLoaded ,setFontsLoaded ] =useState (false);const [handle ] =useState (() =>delayRender ());useEffect (() => {constdelay =delayRender ('Waiting for fonts to be loaded');waitForFonts ().then (() => {continueRender (handle );continueRender (delay );setFontsLoaded (true);}).catch ((err ) => {cancelRender (err );});}, [fontsLoaded ,handle ]);if (!fontsLoaded ) {return null;}return <>{children }</>;};
WaitForFonts.tsxtsx
importReact , {useEffect ,useState } from 'react';import {cancelRender ,continueRender ,delayRender } from 'remotion';import {waitForFonts } from './fonts';export constWaitForFonts :React .FC <{children :React .ReactNode ;}> = ({children }) => {const [fontsLoaded ,setFontsLoaded ] =useState (false);const [handle ] =useState (() =>delayRender ());useEffect (() => {constdelay =delayRender ('Waiting for fonts to be loaded');waitForFonts ().then (() => {continueRender (handle );continueRender (delay );setFontsLoaded (true);}).catch ((err ) => {cancelRender (err );});}, [fontsLoaded ,handle ]);if (!fontsLoaded ) {return null;}return <>{children }</>;};
Then the component can be wrap any other component that calls text measurement APIs:
MyComp.tsxtsx
importReact from 'react';import {regular } from './fonts';import {WaitForFonts } from './WaitForFonts';import {measureText } from '@remotion/layout-utils';constMyCompInner :React .FC = () => {// Safe to call measureText() hereconstmeasurement =measureText ({fontFamily :regular ,fontSize : 14,fontWeight : '400',text : 'Hello world',});return null;};export constMyComp :React .FC = () => {return (<WaitForFonts ><MyCompInner /></WaitForFonts >);};
MyComp.tsxtsx
importReact from 'react';import {regular } from './fonts';import {WaitForFonts } from './WaitForFonts';import {measureText } from '@remotion/layout-utils';constMyCompInner :React .FC = () => {// Safe to call measureText() hereconstmeasurement =measureText ({fontFamily :regular ,fontSize : 14,fontWeight : '400',text : 'Hello world',});return null;};export constMyComp :React .FC = () => {return (<WaitForFonts ><MyCompInner /></WaitForFonts >);};
Use the validateFontIsLoaded
optionv4.0.136
Pass validateFontIsLoaded: true
to any of measureText()
, fitText()
, and fillTextBox()
to validate that the font family you passed is actually loaded.
This will take a second measurement with the fallback font and if it produces the same measurements, it assumes the fallback font was used and will throw an error.
In Remotion v5, this will become the default.
Match all font properties
When measuring text, ensure that all font properties match the ones you are going to use in your video.
This includes fontFamily
, fontSize
, and fontWeight
, letterSpacing
and fontVariantNumeric
.
You could make reusable variables that you reference in both the measuring function and the actual component.
Using variables for font propertiestsx
import {measureText } from '@remotion/layout-utils';consttext = 'Hello world';constfontFamily = 'Inter';constfontWeight = 'bold';constfontSize = 16;// Use the variable in the measurement function:measureText ({text ,fontFamily ,fontWeight ,fontSize ,});// As well as in markup<div style ={{fontFamily ,fontWeight ,fontSize }}>{text }</div >;
Using variables for font propertiestsx
import {measureText } from '@remotion/layout-utils';consttext = 'Hello world';constfontFamily = 'Inter';constfontWeight = 'bold';constfontSize = 16;// Use the variable in the measurement function:measureText ({text ,fontFamily ,fontWeight ,fontSize ,});// As well as in markup<div style ={{fontFamily ,fontWeight ,fontSize }}>{text }</div >;
Be aware of borders and padding
Adding a padding
or a border
to a word will skew the measurements.
Avoid using padding
altogether and use the natural spacing between words.
Instead of border
, use an outline
to add a line outside the text without affecting its layout.
Whitespace
When measuring, the Layout utils will wrap the text in a <span>
with display: inline-block
and white-space: pre
applied.
This will also measure the width of the whitespace characters.
Add those two CSS properties to your markup as well to match it with the measurements.
Server-side rendering
The layout utilities need to be run in a browser.
If you are using them in a component that will be server-side rendered, then we recommend you call the functions in a useEffect
, like on the first example on this page.