Fades a stroke across the ribbon's width — the field for a soft inner core fading toward the edges, or (edge-anchored) an outer rim fading inward.
JavaScript API:
CrossStrokeField
This is the across-the-width member of the StrokeField family: like every
field it turns where into how much
— a scalar weight consumers read by #id (field="#id") — but a stroke field is
defined only along a stroke, weighting each point by its stroke coordinate
rather than its position in space (see the general SpatialField concept for
position-based fields). Here the weight depends on the perpendicular cross-stroke
distance: with the default center-line anchor it is full at the centre and fades
toward the edges (a core); with anchor="edge" it is full at the edges and fades
inward (a rim).
Key attributes. threshold sets how far out the full-weight core extends —
it is a radius from the centreline, so the full core is twice as wide — and
softness sets how gradually the weight fades past that: the smoothstep
transition runs from threshold - softness to threshold + softness (half the
full transition span on each side). By default both are fractions of the local
half-width (threshold-space="fraction" / softness-space="fraction"), so on a
variable-width stroke the core's absolute thickness tracks the local width.
threshold-space and softness-space switch either value independently to an
absolute unit: screen measures it in screen pixels and world in world units,
both FIXED regardless of the local width — e.g. threshold-space="world"
softness-space="screen" holds a constant world-unit core with a crisp
fixed-pixel falloff. anchor picks which side the distance is measured from:
center-line (the default) grows the full-weight region outward from the
centreline (a core), while edge measures inward from the stroke edge, so
anchor="edge" threshold-space="screen" threshold="8" holds a constant
8-pixel outer rim on any variable-width stroke. Neither value is clamped: a
threshold at or past the stroke edge with nonzero softness still shows the
partial falloff that starts inside the edge. Absolute spaces apply during GPU
rendering; the CPU-side evaluate() treats
the values as fractions (the anchor applies on both). A cap-field-mode
option further chooses how the field behaves in the stroke's end caps.
Because a stroke field returns zero off-stroke, it is meaningful only through
a stroke's tonemap, not on a surface.
Related. Sibling stroke fields shape weight along the length instead of across
it: sk-progress-stroke-field ramps from start to end, and
sk-dash-array-stroke-field makes a repeating dash pattern. The usual
consumer is a sk-tonemap applied through a sk-stroke-paint.
threshold-space/softness-space mirror how sk-stroke-paint's
width-space makes the stroke width itself screen- or world-absolute.
Serve these docs to run the live example.
WebGPU needs a secure context — open this page via grunt serve
rather than double-clicking the file. The Markup tab works from disk.
<!-- Four identical squiggles, each tapered thick-to-thin (taper direction
alternates so the row reads clearly), showing how a cross-stroke field
shapes colour ACROSS the stroke width. Each field drives a color-map's `t`
via `intensity-field`, so its cross-stroke weight becomes the palette
lookup. Left to right:
(a) 2-colour, threshold=0.5 (fraction) + 2px softness — a hard core with a
crisp edge; (b) 3-colour, threshold=0.5 + softness=0.5 (fraction) — a
soft symmetric gradient across the width; (c) 2-colour, threshold &
softness both 2px absolute with anchor="edge" — a constant-width edge
band that ignores the taper; (d) same as (c) but a FRACTIONAL edge
threshold, so the band tracks the local width instead. -->
<sk-scene>
<sk-directional-light p="70deg" h="20deg" intensity="1.0"></sk-directional-light>
<sk-ambient-light intensity="0.55"></sk-ambient-light>
<!-- Three-quarter view, auto-framed to the four strokes, with a slow swooping
orbit. Two separate animations so each axis keeps its own pacing: azimuth
turns at a CONSTANT rate (linear, no pause at the seam), while elevation
dips 45°->15°->45° with `ease-in-out` so it glides through the low point
instead of reversing sharply (no bounce). -->
<sk-camera-orbit-control elevation="45deg" azimuth="45deg" fit-targets="#strokes"
fit-margin="0.1" orient-toward-pivot>
<sk-animation duration="32000ms" iterations="Infinity" easing="linear">
<sk-keyframe offset="0" azimuth="0deg"></sk-keyframe>
<sk-keyframe offset="1" azimuth="360deg"></sk-keyframe>
</sk-animation>
<sk-animation duration="16000ms" iterations="Infinity" direction="alternate" easing="ease-in-out">
<sk-keyframe offset="0" elevation="45deg"></sk-keyframe>
<sk-keyframe offset="1" elevation="15deg"></sk-keyframe>
</sk-animation>
</sk-camera-orbit-control>
<sk-orthographic-camera height="13"></sk-orthographic-camera>
<!-- (a) Hard 50% core, crisp 2px edge. -->
<sk-cross-stroke-field id="x-a" cap-field-mode="match" threshold="0.5" softness="2" softness-space="screen"></sk-cross-stroke-field>
<!-- (b) Soft symmetric sweep: a 50% core with a wide fractional falloff. -->
<sk-cross-stroke-field id="x-b" threshold="0.5" softness="0.5"></sk-cross-stroke-field>
<!-- (c) Constant-width edge band: 2px in from the edge, 2px falloff. -->
<sk-cross-stroke-field id="x-c" cap-field-mode="match" anchor="edge" threshold="2" threshold-space="screen"
softness="2" softness-space="screen"></sk-cross-stroke-field>
<!-- (d) Edge band as a FRACTION of the local width, so it tracks the taper. -->
<sk-cross-stroke-field id="x-d" anchor="edge" threshold="0.3"
softness="2" softness-space="screen"></sk-cross-stroke-field>
<!-- Color-maps: (a/c/d) two-stop, (b) the full three-stop palette. -->
<sk-ramp-color-map id="cm-2">
<sk-color-stop stop-offset="0" stop-color="#833AB4"></sk-color-stop>
<sk-color-stop stop-offset="1" stop-color="#FD1D89"></sk-color-stop>
</sk-ramp-color-map>
<sk-ramp-color-map id="cm-3">
<sk-color-stop stop-offset="0" stop-color="#833AB4"></sk-color-stop>
<sk-color-stop stop-offset="0.5" stop-color="#FD1D89"></sk-color-stop>
<sk-color-stop stop-offset="1" stop-color="#FCB045"></sk-color-stop>
</sk-ramp-color-map>
<!-- Each field drives its color-map's `t` (intensity-field), painted as the
stroke's base-color-source. -->
<sk-color-map-source id="src-a" intensity-field="#x-a" color-map="#cm-2"></sk-color-map-source>
<sk-color-map-source id="src-b" intensity-field="#x-b" color-map="#cm-3"></sk-color-map-source>
<sk-color-map-source id="src-c" intensity-field="#x-c" color-map="#cm-2"></sk-color-map-source>
<sk-color-map-source id="src-d" intensity-field="#x-d" color-map="#cm-2"></sk-color-map-source>
<sk-plain-material id="mat-a" base-color="white" base-color-source="#src-a"></sk-plain-material>
<sk-plain-material id="mat-b" base-color="white" base-color-source="#src-b"></sk-plain-material>
<sk-plain-material id="mat-c" base-color="white" base-color-source="#src-c"></sk-plain-material>
<sk-plain-material id="mat-d" base-color="white" base-color-source="#src-d"></sk-plain-material>
<sk-scene-node id="strokes">
<!-- (a) thick head on +z. -->
<sk-spline>
<sk-points-spline-source basis="catmullRom"
points="[[-4.5,0,-5],[-3.94,0,-4.29],[-3.62,0,-3.57],[-3.69,0,-2.86],[-4.11,0,-2.14],[-4.7,0,-1.43],[-5.2,0,-0.71],[-5.4,0,0],[-5.2,0,0.71],[-4.7,0,1.43],[-4.11,0,2.14],[-3.69,0,2.86],[-3.62,0,3.57],[-3.94,0,4.29],[-4.5,0,5]]"></sk-points-spline-source>
<sk-stroke-paint material="#mat-a" width="50" line-cap="round"
width-profile='{"basis":"linear","points":[{"t":0,"width":0.2},{"t":1,"width":1}]}'></sk-stroke-paint>
</sk-spline>
<!-- (b) thick head on -z. -->
<sk-spline>
<sk-points-spline-source basis="catmullRom"
points="[[-1.5,0,-5],[-0.94,0,-4.29],[-0.62,0,-3.57],[-0.69,0,-2.86],[-1.11,0,-2.14],[-1.7,0,-1.43],[-2.2,0,-0.71],[-2.4,0,0],[-2.2,0,0.71],[-1.7,0,1.43],[-1.11,0,2.14],[-0.69,0,2.86],[-0.62,0,3.57],[-0.94,0,4.29],[-1.5,0,5]]"></sk-points-spline-source>
<sk-stroke-paint material="#mat-b" width="50" line-cap="round"
width-profile='{"basis":"linear","points":[{"t":0,"width":1},{"t":1,"width":0.2}]}'></sk-stroke-paint>
</sk-spline>
<!-- (c) thick head on +z. -->
<sk-spline>
<sk-points-spline-source basis="catmullRom"
points="[[1.5,0,-5],[2.06,0,-4.29],[2.38,0,-3.57],[2.31,0,-2.86],[1.89,0,-2.14],[1.3,0,-1.43],[0.8,0,-0.71],[0.6,0,0],[0.8,0,0.71],[1.3,0,1.43],[1.89,0,2.14],[2.31,0,2.86],[2.38,0,3.57],[2.06,0,4.29],[1.5,0,5]]"></sk-points-spline-source>
<sk-stroke-paint material="#mat-c" width="50" line-cap="round"
width-profile='{"basis":"linear","points":[{"t":0,"width":0.2},{"t":1,"width":1}]}'></sk-stroke-paint>
</sk-spline>
<!-- (d) thick head on -z. -->
<sk-spline>
<sk-points-spline-source basis="catmullRom"
points="[[4.5,0,-5],[5.06,0,-4.29],[5.38,0,-3.57],[5.31,0,-2.86],[4.89,0,-2.14],[4.3,0,-1.43],[3.8,0,-0.71],[3.6,0,0],[3.8,0,0.71],[4.3,0,1.43],[4.89,0,2.14],[5.31,0,2.86],[5.38,0,3.57],[5.06,0,4.29],[4.5,0,5]]"></sk-points-spline-source>
<sk-stroke-paint material="#mat-d" width="50" line-cap="round"
width-profile='{"basis":"linear","points":[{"t":0,"width":1},{"t":1,"width":0.2}]}'></sk-stroke-paint>
</sk-spline>
</sk-scene-node>
</sk-scene>
| Attribute | Type | Default | Description |
|---|---|---|---|
id |
<id> |
— | The element's unique identifier — the standard HTML global id attribute. |
remap-curveanimatable |
<remap-curve> |
— | Optional curve that reshapes this field's raw [0, 1] output. |
remap-phaseanimatable |
<number> |
0 |
Horizontal remap phase — an animatable scalar that translates a present |
thresholdanimatable |
<number> |
— | Distance from center at which weight starts falling off. |
softnessanimatable |
<number> |
— | Width of the smooth transition zone. |
cap-field-modeanimatable |
<cap-field-mode> ("none" | "match") |
— | How the field gradient behaves in cap regions. |
threshold-spaceanimatable |
<field-space> ("fraction" | "screen" | "world") |
— | The unit space CrossStrokeField.threshold is expressed in. |
softness-spaceanimatable |
<field-space> ("fraction" | "screen" | "world") |
— | The unit space CrossStrokeField.softness is expressed in. |
anchoranimatable |
<field-anchor> ("center-line" | "edge") |
— | Where the cross distance is measured from (centreline or edge). |