Custom JavaScript

Custom JavaScript lets you add interactive behavior to any element. Your code runs when the element mounts in the live preview and on the published site.

The element variable

Your code receives an element variable that references the DOM node of the selected element. Use it to manipulate the element directly.

element.addEventListener('click', () => {
  element.style.backgroundColor = '#' + Math.floor(Math.random()*16777215).toString(16);
});

The element is the actual DOM node, so you have full access to the DOM API — querySelector, getBoundingClientRect, classList, style, and everything else.

Importing libraries

You can import external libraries using dynamic import() from a CDN like esm.sh:

import('https://esm.sh/canvas-confetti').then(({ default: confetti }) => {
  element.addEventListener('click', () => {
    const rect = element.getBoundingClientRect();
    confetti({
      origin: {
        x: (rect.left + rect.width / 2) / window.innerWidth,
        y: (rect.top + rect.height / 2) / window.innerHeight,
      }
    });
  });
});

Libraries like Three.js, OGL, GSAP, and others work this way. The import is cached by the browser after the first load.

Lifecycle

Your code runs once when the element appears on the page. Use the Play button to preview it in the builder, and press Stop to end the preview.

Using @controls

The @controls system lets you define UI controls that inject variables into your code. Each control becomes a const declaration at the top of your code.

/** @controls {
  speed: { type: "slider", label: "Speed", min: 10, max: 200, step: 10, default: 50 },
  color: { type: "color", label: "Color", default: "#ff0000" },
  loop: { type: "toggle", label: "Loop", default: true }
} */
 
// These variables are available because of @controls:
// const speed = 50;
// const color = "#ff0000";
// const loop = true;
 
element.style.transition = `all ${speed}ms ease`;
element.style.color = color;

When you change a control value in the toolbar, the default value in the code is updated and the preview restarts with the new values.

See Controls for all available control types.

Examples

Parallax scroll effect

window.addEventListener('scroll', () => {
  const rect = element.getBoundingClientRect();
  const speed = 0.3;
  const yOffset = (window.innerHeight / 2 - rect.top) * speed;
  element.style.transform = `translateY(${yOffset}px)`;
});

WebGL background with OGL

import('https://esm.sh/ogl').then(({ Renderer, Program, Mesh, Triangle }) => {
  const renderer = new Renderer({ alpha: true });
  const gl = renderer.gl;
  gl.canvas.style.cssText = 'width:100%;height:100%;display:block';
  element.appendChild(gl.canvas);
 
  // ... shader setup and render loop
});

Typed text effect

const text = element.textContent;
element.textContent = '';
let i = 0;
 
const interval = setInterval(() => {
  if (i < text.length) {
    element.textContent += text[i];
    i++;
  } else {
    clearInterval(interval);
  }
}, 50);

Tips

  • Use element.clientWidth and element.clientHeight for sizing
  • You can append children, create canvases, and add event listeners to element
  • Avoid modifying element.style.position or element.style.width/height as this may conflict with the builder layout