Transforms
Reposition, rotate, and scale any shader component in UV space
Every component accepts a transform prop that moves, rotates, and scales it in UV space - the coordinate system the shader uses to sample its output.
This is different from CSS transforms. A CSS transform moves the canvas element in the DOM layout. A shader transform shifts how the shader samples its coordinate system, so the effect itself shifts without touching the DOM.
<!-- CSS transform: moves the DOM element -->
<Shader class="-translate-x-4">...</Shader>
<!-- Shader transform: shifts content within the canvas -->
<Shader>
<LinearGradient :transform="{ offsetX: -0.2 }" />
</Shader>Transform properties
Pass a partial object to the transform prop - only include what you want to change:
| Prop | Type | Default | Description |
|---|---|---|---|
offsetX |
number |
0 |
Horizontal shift. -1 to +1. -1 shifts content fully left, +1 fully right. |
offsetY |
number |
0 |
Vertical shift. -1 to +1. |
rotation |
number |
0 |
Rotation in degrees. Positive = clockwise. |
scale |
number |
1 |
Scale multiplier. 0.5 = half size, 2 = double. |
anchorX |
number |
0.5 |
Horizontal pivot point for rotation and scale. 0 = left, 1 = right. |
anchorY |
number |
0.5 |
Vertical pivot point. 0 = top, 1 = bottom. |
edges |
string |
'transparent' |
What to show when the transform pushes content out of bounds. See below. |
Basic usage
<Shader class="w-full h-64">
<!-- Rotate 45 degrees -->
<LinearGradient :transform="{ rotation: 45 }" />
<!-- Scale up and shift right -->
<Circle :transform="{ scale: 1.5, offsetX: 0.2 }" />
<!-- Multiple transforms together -->
<Swirl :transform="{ rotation: 30, scale: 0.8, offsetY: -0.1 }" />
</Shader>You only need to include the properties you want to change - unspecified values stay at their defaults.
Anchor point
The anchor point controls where rotation and scale are applied from. The default 0.5, 0.5 anchors to the center of the canvas.
{ anchorX: 0, anchorY: 0 }- top-left corner{ anchorX: 0.5, anchorY: 0.5 }- center (default){ anchorX: 1, anchorY: 1 }- bottom-right corner
<!-- Rotates around the center (default) -->
<Circle :transform="{ rotation: 45 }" />
<!-- Rotates around the top-left corner -->
<Circle :transform="{ rotation: 45, anchorX: 0, anchorY: 0 }" />
<!-- Scales from the top edge -->
<LinearGradient :transform="{ scale: 2, anchorY: 0 }" />Edge modes
When a transform shifts or scales content beyond the canvas boundaries, the edges property controls what fills the out-of-bounds area:
| Value | Behavior |
|---|---|
transparent |
Out-of-bounds pixels are fully transparent (default) |
stretch |
The edge pixels repeat to fill |
mirror |
Content reflects at the edges like a mirror |
wrap |
Content tiles seamlessly |
<Shader class="w-full h-64">
<!-- Offset with wrapping - tiles the effect -->
<LinearGradient :transform="{ offsetX: 0.3, edges: 'wrap' }" />
<!-- Offset with mirror - reflects the edge -->
<Swirl :transform="{ offsetX: 0.3, edges: 'mirror' }" />
</Shader>Animating transforms
Transforms are reactive props - you can bind them to state and animate them:
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const rotation = ref(0)
let animFrame
onMounted(() => {
const animate = () => {
rotation.value = (rotation.value + 0.5) % 360
animFrame = requestAnimationFrame(animate)
}
animFrame = requestAnimationFrame(animate)
})
onUnmounted(() => cancelAnimationFrame(animFrame))
</script>
<template>
<Shader class="w-full h-64">
<LinearGradient :transform="{ rotation }" />
</Shader>
</template>For continuous animations like rotation, consider using a Prop Driver instead - the auto-animate driver handles this declaratively without any component code.
Performance
Transforms have zero overhead when all values are at their defaults. The renderer checks for non-default values before activating the UV transformation pass.
Once a non-default transform is applied, a render-to-texture (RTT) pass activates for that component. This RTT persists for the lifetime of the component - even if values return to defaults - to prevent a recompilation flash during animations.
The practical implication: if you're conditionally showing/hiding a transform during an animation, the small RTT cost is paid once and stays, rather than toggling on and off.