threlte logo
@threlte/extras

<LinearGradientTexture>

A reactive linear gradient texture. The underlying texture uses an OffscreenCanvas and a CanvasTexture and is assigned the same colorspace as the renderer.

<script lang="ts">
  import Scene from './Scene.svelte'
  import type { ToneMapping, Wrapping } from 'three'
  import { Canvas } from '@threlte/core'
  import { Color, Folder, List, Pane, Slider } from 'svelte-tweakpane-ui'
  import {
    ClampToEdgeWrapping,
    MirroredRepeatWrapping,
    RepeatWrapping,
    ACESFilmicToneMapping,
    AgXToneMapping,
    CineonToneMapping,
    LinearToneMapping,
    NeutralToneMapping,
    NoToneMapping,
    ReinhardToneMapping
  } from 'three'

  const toneMappingOptions: Record<PropertyKey, ToneMapping> = {
    ACESFilmic: ACESFilmicToneMapping,
    AgX: AgXToneMapping,
    Cineon: CineonToneMapping,
    Linear: LinearToneMapping,
    NeutralToneMapping,
    None: NoToneMapping,
    Reinhard: ReinhardToneMapping
  }

  const wrappingOptions: Record<PropertyKey, Wrapping> = {
    ClampToEdge: ClampToEdgeWrapping,
    MirroredRepeat: MirroredRepeatWrapping,
    Repeat: RepeatWrapping
  }

  const canvasSize = 1024

  let sceneClearColor = $state('#000000')
  let sceneToneMapping = $state(AgXToneMapping)

  let gradientStartColor = $state('#ff00ff')
  let gradientEndColor = $state('#ffff00')
  let gradientStartX = $state(0)
  let gradientStartY = $state(0)
  let gradientEndX = $state(0)
  let gradientEndY = $state(canvasSize)

  let textureCenterX = $state(0)
  let textureCenterY = $state(0)
  let textureOffsetX = $state(0)
  let textureOffsetY = $state(0)
  let textureRepeatX = $state(1)
  let textureRepeatY = $state(1)
  let textureRotationDegrees = $state(0)
  let textureWrapS: Wrapping = $state(ClampToEdgeWrapping)
  let textureWrapT: Wrapping = $state(ClampToEdgeWrapping)

  let textureRotation = $derived((Math.PI / 180) * textureRotationDegrees)
</script>

<Pane position="fixed">
  <List
    bind:value={sceneToneMapping}
    options={toneMappingOptions}
    label="tone mapping"
  />
  <Color
    bind:value={sceneClearColor}
    label="clear color"
  />
  <Folder title="gradient props">
    <Color
      bind:value={gradientStartColor}
      label="start color"
    />
    <Color
      bind:value={gradientEndColor}
      label="end color"
    />
    <Slider
      bind:value={gradientStartX}
      label="start x"
      min={0}
      max={canvasSize}
      step={1}
    />
    <Slider
      bind:value={gradientStartY}
      label="start y"
      min={0}
      max={canvasSize}
      step={1}
    />
    <Slider
      bind:value={gradientEndX}
      label="end x"
      min={0}
      max={canvasSize}
      step={1}
    />
    <Slider
      bind:value={gradientEndY}
      label="end y"
      min={0}
      max={canvasSize}
      step={1}
    />
  </Folder>
  <Folder title="texture props">
    <List
      bind:value={textureWrapS}
      label="wrapS"
      options={wrappingOptions}
    />
    <List
      bind:value={textureWrapT}
      label="wrapT"
      options={wrappingOptions}
    />
    <Slider
      label="centerX"
      bind:value={textureCenterX}
      min={-0.5}
      max={1.5}
      step={0.5}
    />
    <Slider
      label="centerY"
      bind:value={textureCenterY}
      min={-0.5}
      max={1.5}
      step={0.5}
    />
    <Slider
      label="offsetX"
      bind:value={textureOffsetX}
      min={-2}
      max={2}
      step={1}
    />
    <Slider
      label="offsetY"
      bind:value={textureOffsetY}
      min={-2}
      max={2}
      step={1}
    />
    <Slider
      label="repeatX"
      bind:value={textureRepeatX}
      min={0}
      max={5}
      step={1}
    />
    <Slider
      label="repeatY"
      bind:value={textureRepeatY}
      min={0}
      max={5}
      step={1}
    />
    <Slider
      label="rotation (degrees)"
      bind:value={textureRotationDegrees}
      min={-360}
      max={360}
      step={1}
    />
  </Folder>
</Pane>

<div>
  <Canvas>
    <Scene
      {gradientEndColor}
      {gradientEndX}
      {gradientEndY}
      {gradientStartColor}
      {gradientStartX}
      {gradientStartY}
      {sceneClearColor}
      {sceneToneMapping}
      {textureCenterX}
      {textureCenterY}
      {textureOffsetX}
      {textureOffsetY}
      {textureRepeatX}
      {textureRepeatY}
      {textureRotation}
      {textureWrapS}
      {textureWrapT}
      {canvasSize}
    />
  </Canvas>
</div>

<style>
  div {
    height: 100%;
  }
</style>
<script lang="ts">
  import type { ColorRepresentation, ToneMapping, Wrapping } from 'three'
  import type { GradientStop } from '@threlte/extras'
  import { DoubleSide } from 'three'
  import { LinearGradientTexture, OrbitControls } from '@threlte/extras'
  import { T, useThrelte } from '@threlte/core'

  type SceneProps = {
    canvasSize: number
    gradientEndColor: ColorRepresentation
    gradientEndX: number
    gradientEndY: number
    gradientStartColor: ColorRepresentation
    gradientStartX: number
    gradientStartY: number
    sceneClearColor: ColorRepresentation
    sceneToneMapping: ToneMapping
    textureCenterX: number
    textureCenterY: number
    textureOffsetX: number
    textureOffsetY: number
    textureRepeatX: number
    textureRepeatY: number
    textureRotation: number
    textureWrapS: Wrapping
    textureWrapT: Wrapping
  }

  let {
    canvasSize,
    gradientEndColor,
    gradientEndX,
    gradientEndY,
    gradientStartColor,
    gradientStartX,
    gradientStartY,
    sceneClearColor,
    sceneToneMapping,
    textureCenterX,
    textureCenterY,
    textureOffsetX,
    textureOffsetY,
    textureRepeatX,
    textureRepeatY,
    textureRotation,
    textureWrapS,
    textureWrapT
  }: SceneProps = $props()

  let stops: GradientStop[] = $derived([
    { color: gradientStartColor, offset: 0 },
    { color: gradientEndColor, offset: 1 }
  ])

  const { invalidate, renderer, toneMapping } = useThrelte()

  $effect(() => {
    toneMapping.set(sceneToneMapping)
    invalidate()
  })

  $effect(() => {
    renderer.setClearColor(sceneClearColor)
    invalidate()
  })
</script>

<T.PerspectiveCamera
  makeDefault
  position.z={5}
>
  <OrbitControls />
</T.PerspectiveCamera>

<T.Mesh scale={2}>
  <T.PlaneGeometry />
  <T.MeshBasicMaterial side={DoubleSide}>
    <LinearGradientTexture
      width={canvasSize}
      height={canvasSize}
      startX={gradientStartX}
      startY={gradientStartY}
      endX={gradientEndX}
      endY={gradientEndY}
      center.x={textureCenterX}
      center.y={textureCenterY}
      offset.x={textureOffsetX}
      offset.y={textureOffsetY}
      repeat.x={textureRepeatX}
      repeat.y={textureRepeatY}
      rotation={textureRotation}
      wrapS={textureWrapS}
      wrapT={textureWrapT}
      {stops}
    />
  </T.MeshBasicMaterial>
</T.Mesh>

Attaching the Texture

The texture is automatically attached to the map property of its parent. You can disable this behaviour by setting the attach prop to false. This may be useful if you want to create the texture but use it somewhere else.

<script>
  let texture = null
</script>

<LinearGradientTexture
  attach={false}
  bind:ref={texture}
/>

<SomeComponent {texture} />

Gradient Stops

<LinearGradientTexture> accepts a stops prop which is an array of color stops that define the gradient. A stop is defined by two things; an offset and a color. Gradient stops are identical to how you would use them with a 2D context, notably the offset should be a number between 0 and 1 inclusive. Stop colors can be any valid color representation in ThreeJS. Here are a couple examples of valid stops.

<LinearGradientTexture
  stops={[
    { color: 'black', offset: 0 },
    { color: 'white', offset: 1 }
  ]}
/>

<LinearGradientTexture
  stops={[
    { color: '#00ffff', offset: 0 },
    { color: '#ff00ff', offset: 0.5 },
    { color: '#ffff00', offset: 1 }
  ]}
/>

You can even mix and match color representations

<LinearGradientTexture
  stops={[
    { color: 'red', offset: 0 },
    { color: 0xff_00_00, offset: 0.25 },
    { color: 'rgb(255, 0, 0)', offset: 0.5 },
    { color: '#ff0000', offset: 0.75 },
    { color: new Color(new Color(new Color())).set(1, 0, 0), offset: 1 }
  ]}
/>

All of the colors above are valid representations of the color red.

Gradient Start Point and End Point

You can control the gradient start point and end point with the startX, startY, endX, and endY props. For example, the props for a gradient that starts at the bottom left corner of the texture and ends at the top right corner would be:

<LinearGradientTexture
  startX={0}
  startY={height}
  endX={width}
  endY={0}
/>

Adjusting Scene Colors

If the colors in your scene do not match the color in your stops, you may need to adjust the tone mapping of the scene. ToneMapping constants are imported from the three library.

<script>
  import { useThrelte } from '@threlte/core'
  import { LinearToneMapping } from 'three'

  const { toneMapping } = useThrelte()

  toneMapping.set(LinearToneMapping)
</script>

Component Signature

<LinearGradientTexture> extends <T . CanvasTexture> and supports all its props, slot props, bindings and events.

Props

name
type
required
default
description

endX
number
no
0
x-axis coordinate of the gradient's end point

endY
number
no
height
y-axis coordinate of the gradient's end point

height
number
no
1024
height of the texture's canvas

startX
number
no
0
x-axis coordinate of the gradient's start point

startY
number
no
0
y-axis coordinate of the gradient's start point

stops
GradientStop[]
no
[{color: '#000000', offset: 0}, {color: '#ffffff', offset: 1}]
list of stops applied to the gradient. more info at https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createLinearGradient

width
number
no
1024
width of the texture's canvas