1var _____WB$wombat$assign$function_____=function(name){return (globalThis._wb_wombat && globalThis._wb_wombat.local_init && globalThis._wb_wombat.local_init(name))||globalThis[name];};if(!globalThis.__WB_pmw){globalThis.__WB_pmw=function(obj){this.__WB_source=obj;return this;}}{ 2let window = _____WB$wombat$assign$function_____("window"); 3let self = _____WB$wombat$assign$function_____("self"); 4let document = _____WB$wombat$assign$function_____("document"); 5let location = _____WB$wombat$assign$function_____("location"); 6let top = _____WB$wombat$assign$function_____("top"); 7let parent = _____WB$wombat$assign$function_____("parent"); 8let frames = _____WB$wombat$assign$function_____("frames"); 9let opener = _____WB$wombat$assign$function_____("opener"); 10"use strict";(self.webpackChunk_motion_canvas_docs=self.webpackChunk_motion_canvas_docs||[]).push([[494],{876:(e,n,t)=>{t.d(n,{Zo:()=>p,kt:()=>h});var o=t(2784);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function r(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);n&&(o=o.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,o)}return t}function i(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments[n]?arguments[n]:{};n%2?r(Object(t),!0).forEach((function(n){a(e,n,t[n])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(t)):r(Object(t)).forEach((function(n){Object.defineProperty(e,n,Object.getOwnPropertyDescriptor(t,n))}))}return e}function s(e,n){if(null==e)return{};var t,o,a=function(e,n){if(null==e)return{};var t,o,a={},r=Object.keys(e);for(o=0;o<r.length;o++)t=r[o],n.indexOf(t)>=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(o=0;o<r.length;o++)t=r[o],n.indexOf(t)>=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var l=o.createContext({}),c=function(e){var n=o.useContext(l),t=n;return e&&(t="function"==typeof e?e(n):i(i({},n),e)),t},p=function(e){var n=c(e.components);return o.createElement(l.Provider,{value:n},e.children)},d="mdxType",m={inlineCode:"code",wrapper:function(e){var n=e.children;return o.createElement(o.Fragment,{},n)}},u=o.forwardRef((function(e,n){var t=e.components,a=e.mdxType,r=e.originalType,l=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),d=c(t),u=a,h=d["".concat(l,".").concat(u)]||d[u]||m[u]||r;return t?o.createElement(h,i(i({ref:n},p),{},{components:t})):o.createElement(h,i({ref:n},p))}));function h(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var r=t.length,i=new Array(r);i[0]=u;var s={};for(var l in n)hasOwnProperty.call(n,l)&&(s[l]=n[l]);s.originalType=e,s[d]="string"==typeof e?e:a,i[1]=s;for(var c=2;c<r;c++)i[c]=t[c];return o.createElement.apply(null,i)}return o.createElement.apply(null,t)}u.displayName="MDXCreateElement"},2665:(e,n,t)=>{t.d(n,{Z:()=>f});var o=t(2784);const a="container_lQCo",r="small_As57",i="banner_A4QS",s="player_tELG",l="link_BTzN",c="icon_wSGd";var p=t(7896);function d(e){let{width:n=24,height:t=24,...a}=e;return o.createElement("svg",(0,p.Z)({viewBox:"0 0 24 24",width:n,height:t,"aria-hidden":!0},a),o.createElement("path",{d:"M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z",fill:"currentColor"}))}var m=t(9817),u=t(6277);function h(e){let{name:n}=e;const t=`http://web.archive.org/web/20230317133404/https://github.com/motion-canvas/motion-canvas/blob/main/packages/examples/src/scenes/${n}.tsx`;return o.createElement(m.Z,{to:t,className:(0,u.Z)("padding--sm",l)},o.createElement("span",null,"View source code"),o.createElement(d,{className:c}))}function f(e){let{name:n,banner:t,small:l,link:c}=e;return o.createElement("div",{className:(0,u.Z)(a,t&&i,l&&r)},o.createElement("motion-canvas-player",{class:s,src:`/examples/${n}.js`,auto:t}),o.createElement(h,{name:c||n}))}t(1263).Z.canUseDOM&&Promise.all([t.e(5392),t.e(9106)]).then(t.bind(t,9106))},6384:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>l,default:()=>u,frontMatter:()=>s,metadata:()=>c,toc:()=>d});
10var o=t(7896),a=(t(2784),t(876)),r=t(2665),i=t(8128);const s={sidebar_position:3,slug:"/custom-components"},l="Custom Components",c={unversionedId:"components/custom-components",id:"components/custom-components",title:"Custom Components",description:"Components are classes like Rect and",source:"@site/docs/components/custom-components.mdx",sourceDirName:"components",slug:"/custom-components",permalink:"/docs/custom-components",draft:!1,editUrl:"http://web.archive.org/web/20230317133404/https://github.com/motion-canvas/motion-canvas/blob/main/packages/docs/docs/components/custom-components.mdx",tags:[],version:"current",lastUpdatedBy:"Jacob",sidebarPosition:3,frontMatter:{sidebar_position:3,slug:"/custom-components"},sidebar:"docs",previous:{title:"LaTeX",permalink:"/docs/nodes"},next:{title:"Advanced",permalink:"/docs/category/advanced"}},p={},d=[],m={toc:d};function u(e){let{components:n,...t}=e;return(0,a.kt)("wrapper",(0,o.Z)({},m,t,{components:n,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"custom-components"},"Custom Components"),(0,a.kt)(r.Z,{name:"components",banner:!0,mdxType:"AnimationPlayer"}),(0,a.kt)("p",null,"Components are classes like ",(0,a.kt)("a",{parentName:"p",href:"/api/2d/components/Rect"},(0,a.kt)("inlineCode",{parentName:"a"},"Rect"))," and\n",(0,a.kt)("a",{parentName:"p",href:"/api/2d/components/Circle"},(0,a.kt)("inlineCode",{parentName:"a"},"Circle"))," that can abstract rendering and data\nfunctionality into reusable, modular pieces. To use a component in a scene, add\nit to the view and provide arguments to the component."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},"<Switch initialState={false} />\n")),(0,a.kt)("p",null,"To define what arguments a component will take, first define an interface. All\nproperties of the interface must be wrapped in\n",(0,a.kt)("a",{parentName:"p",href:"/api/core/signals#SignalValue"},(0,a.kt)("inlineCode",{parentName:"a"},"SignalValue<>"))," as such:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},"// You can extend an existing props interface\n// such as LayoutProps, ShapeProps or NodeProps to\n// include their properties alongside the ones you\n// define\n\nexport interface SwitchProps extends NodeProps {\n initialState?: SignalValue<boolean>;\n\n // We don't use color here because we want\n // to be able to pass hex strings and rgb\n // values to accent rather than a `Color`\n accent?: SignalValue<PossibleColor>;\n}\n")),(0,a.kt)("p",null,"Next, create a class for your components. The component class must extend\n",(0,a.kt)("a",{parentName:"p",href:"/api/2d/components/Node"},(0,a.kt)("inlineCode",{parentName:"a"},"Node"))," or one of its subclasses. If you don't want to\ninherit any methods from an existing component, extend your class from ",(0,a.kt)("inlineCode",{parentName:"p"},"Node"),".\nWe advise extending from the component most similar to the component you are\nbuilding. For instance, if you were to make a component including a\n",(0,a.kt)("a",{parentName:"p",href:"/api/2d/components/Layout"},(0,a.kt)("inlineCode",{parentName:"a"},"Layout")),", you should extend\n",(0,a.kt)("a",{parentName:"p",href:"/api/2d/components/Layout"},(0,a.kt)("inlineCode",{parentName:"a"},"Layout"))," and\n",(0,a.kt)("a",{parentName:"p",href:"/api/2d/components/LayoutProps"},(0,a.kt)("inlineCode",{parentName:"a"},"LayoutProps")),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},"export interface SwitchProps extends NodeProps {\n // properties\n}\n\nexport class Switch extends Node {\n // implementation\n}\n")),(0,a.kt)("p",null,"To use the properties defined in the interface, your class ",(0,a.kt)("em",{parentName:"p"},"must")," contain a\nproperty with the same name. Motion Canvas provides type decorators to\nfacilitate this like ",(0,a.kt)("inlineCode",{parentName:"p"},"@initial()")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"@signal()"),". Click ",(0,a.kt)("a",{parentName:"p",href:"/docs/signals"},"here"),"\nfor more information on signals."),(0,a.kt)("p",null,"Here is an example of how you would define such properties:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},"export class Switch extends Node {\n // @initial - optional, sets the property to an\n // initial value if it was not provided.\n @initial(false)\n // @signal - is required by motion canvas\n // for every prop that was passed in.\n @signal()\n public declare readonly initialState: SimpleSignal<boolean, this>;\n\n @initial('#68ABDF')\n // @colorSignal - some complex types provide a dedicated decorator for\n // signals that takes care of parsing.\n // In this case, `accent` will automatically convert strings into `Color`s\n @colorSignal()\n public declare readonly accent: ColorSignal<this>;\n // ...\n}\n")),(0,a.kt)("p",null,"Notice how colors are wrapped in ",(0,a.kt)("inlineCode",{parentName:"p"},"ColorSignal<>")," while any other type (even\nuser-defined ones) are wrapped in ",(0,a.kt)("inlineCode",{parentName:"p"},"SimpleSignal<>"),". The type does not need to be\npassed to color signal as Motion Canvas knows that it must be of a\ncolor-resolvable type. In both, the class is passed at the end of the wrapper to\nregister the signal to the class. Properties must be initialised with the\n",(0,a.kt)("inlineCode",{parentName:"p"},"public"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"declare")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"readonly")," keywords."),(0,a.kt)("p",null,"Normal properties can be defined as normal. For example:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},"export class Switch extends Node {\n public constructor(props?: SwitchProps) {\
10n super({\n // If you wanted to ensure that layout was always\n // true for this component, you could add it here\n // as such:\n // layout: true\n ...props,\n });\n // ...\n }\n}\n")),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"props")," parameter can also be useful outside the ",(0,a.kt)("inlineCode",{parentName:"p"},"super()")," call to access\nyour data elsewhere. For example, if you were building a component to display an\narray, you could use props to set the color of every\n",(0,a.kt)("a",{parentName:"p",href:"/api/2d/components/Rect"},"Rect")," in the array."),(0,a.kt)("p",null,"Now we can add elements to the view by using ",(0,a.kt)("inlineCode",{parentName:"p"},"this.add()"),", much like you would\nadd to a scene's view:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},"export class Switch extends Node {\n public constructor(props?: SwitchProps) {\n // ...\n this.add(\n <Rect>\n <Circle />\n </Rect>,\n );\n }\n}\n")),(0,a.kt)("p",null,"Since this is a class, you can also add methods. This is especially useful when\nwanting to animate a component easily. Here is an example of a method for\ntoggling our switch:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},"export class Switch extends Node {\n // ...\n public *toggle(duration: number) {\n yield* all(\n tween(duration, value => {\n // ...\n }),\n tween(duration, value => {\n // ...\n }),\n );\n this.isOn = !this.isOn;\n }\n}\n")),(0,a.kt)("p",null,"Here is the source code for the component we have built throughout this guide:"),(0,a.kt)(i.Z,{language:"tsx",mdxType:"CodeBlock"},"import {Circle, Node, NodeProps, Rect} from '@motion-canvas/2d/lib/components';\nimport {easeInOutCubic, tween} from '@motion-canvas/core/lib/tweening';\nimport {\n Color,\n ColorSignal,\n PossibleColor,\n} from '@motion-canvas/core/lib/types/Color';\nimport {colorSignal, initial, signal} from '@motion-canvas/2d/lib/decorators';\nimport {\n createSignal,\n SignalValue,\n SimpleSignal,\n} from '@motion-canvas/core/lib/signals';\nimport {createRef} from '@motion-canvas/core/lib/utils';\nimport {all} from '@motion-canvas/core/lib/flow';\n\nexport interface SwitchProps extends NodeProps {\n initialState?: SignalValue<boolean>;\n accent?: SignalValue<PossibleColor>;\n}\n\nexport class Switch extends Node {\n @initial(false)\n @signal()\n public declare readonly initialState: SimpleSignal<boolean, this>;\n\n @initial('#68ABDF')\n @colorSignal()\n public declare readonly accent: ColorSignal<this>;\n\n private isOn: boolean;\n private readonly indicatorPosition = createSignal(0);\n private readonly offColor = new Color('#242424');\n private readonly indicator = createRef<Circle>();\n private readonly container = createRef<Rect>();\n\n public constructor(props?: SwitchProps) {\n super({\n ...props,\n });\n\n this.isOn = this.initialState();\n this.indicatorPosition(this.isOn ? 50 : -50);\n\n this.add(\n <Rect\n ref={this.container}\n fill={this.isOn ? this.accent() : this.offColor}\n size={[200, 100]}\n radius={100}\n >\n <Circle\n x={() => this.indicatorPosition()}\n ref={this.indicator}\n size={[80, 80]}\n fill=\"#ffffff\"\n />\n </Rect>,\n );\n }\n\n public *toggle(duration: number) {\n yield* all(\n tween(duration, value => {\n const oldColor = this.isOn ? this.accent() : this.offColor;\n const newColor = this.isOn ? this.offColor : this.accent();\n\n this.container().fill(\n Color.lerp(oldColor, newColor, easeInOutCubic(value)),\n );\n }),\n\n tween(duration, value => {\n const currentPos = this.indicator().position();\n\n this.indicatorPosition(\n easeInOutCubic(value, currentPos.x, this.isOn ? -50 : 50),\n );\n }),\n );\n this.isOn = !this.isOn;\n }\n}\n"))}u.isMDXComponent=!0}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.