Documentation
What follows are the main classes an average developer will encounter when working with Blotter. Many classes and utilities are left out of this documentation, as they are more internal to the codebase rather than part of the interface one would need to use Blotter as it comes. If you would like to see Blotter laid bare, please view the source.
Everything documented here is a named export of the blotter.ts package. The
ready-made effects live under the blotter.ts/materials subpath.
import { Blotter, Material, ShaderMaterial, Text, shaders } from "blotter.ts";
import { ChannelSplitMaterial } from "blotter.ts/materials"; Blotter
Blotter is the orchestrator. It packs all of its texts into one atlas,
renders them through the material's shader in a single draw call, and hands each text
its own output canvas. Whenever you want to apply a material to texts on your page, you
will instantiate a new object of the Blotter class.
-
construction
const blotter = new Blotter(material, options);Create a new instance of
Blotterwherematerialis an instance of anyMaterialandoptionsis an optional object with any of the following optional parameters. The constructor throws when the device does not support WebGL; checkisWebGLSupported()first if you need a fallback.-
texts
options.textsAn array of
Textobjects, or optionally a singleTextobject. Any texts given to an instance ofBlotterwill be drawn according to the instance's material. -
ratio
options.ratioThe backing store size in relation to the canvas element, or otherwise known as the
devicePixelRatio. This property tells Blotter what pixel ratio to draw texts for in relation to the screen, and will affect the clarity of rendered texts on retina devices. Blotter determines this property by default to ensure the most appropriate pixel ratio for your user's device, so it's unlikely you'll need to set this yourself. -
autobuild
options.autobuildTells the instance of
Blotterwhether or not to immediately build and configure itself using the suppliedMaterialinstance and optional parameters such as suppliedTextobjects. By default, Blotter will assume this to betrue. If you intend to add additional texts or change the material prior to use, set this tofalseand later callblotter.update()to trigger a fresh build. -
autostart
options.autostartTells the instance of
Blotterwhether or not to immediately begin rendering and updating universal uniforms. By default, Blotter will assume this to betrue. -
autoplay
options.autoplayTells the instance of
Blotterwhether or not to immediately begin the render loops for allRenderScopeobjects created for each of its texts. By default, Blotter will assume this to betrue. Each draw will happen onrequestAnimationFrame.
Await
blotter.ready(or listen for thereadyevent) to perform work after the instance has finished building itself.const blotter = new Blotter(material, { texts: [text] }); blotter.forText(text)?.appendTo(document.body); await blotter.ready; -
-
ready
blotter.readyA
Promisethat resolves with the instance after its first successful build. This is the primary way to wait for aBlotter; thereadyevent still fires for the same moment. -
material
blotter.materialA reference to the
Materialinstance being used to render your texts. Assigning a new material (or callingsetMaterial) after yourBlotterobject's initialization requires that your instance be rebuilt by callingblotter.update(). -
texts
blotter.textsA read-only array of the
Textobjects being rendered by your instance ofBlotter. UseaddTextsandremoveTextsto change it. -
ratio
blotter.ratioThe pixel ratio the instance renders at. Read-only after construction.
-
imageData
blotter.imageDataThe
ImageDataof the whole back buffer from the most recent frame, orundefinedbefore the first render. EachRenderScopecopies its own rectangle out of this. -
update
blotter.update()Rebuild the atlas and shader from the current texts and material. Returns a
Promisethat resolves when the rebuild has settled. Concurrent calls coalesce: one build runs at a time, with at most one trailing rebuild queued. Text, property and material changes call this for you; you only need it aftersetMaterial,addTexts,removeTexts, or whenautobuildisfalse.The instance emits
readyon the first completed build andupdateon every later one. -
start
blotter.start()Tells the instance of
Blotterto begin rendering and updating universal uniforms. At the time of each draw onrequestAnimationFramethe instance will emit arenderevent. -
stop
blotter.stop()Tells the instance of
Blotterto stop rendering and updating universal uniforms. -
teardown
blotter.teardown()Disposes the instance's render target and removes every listener it registered on its texts and material. Call
stop()first, thenteardown(), when you are done with an instance; the shared WebGL context stays alive for other instances. -
setMaterial
blotter.setMaterial(material)Sets the
Materialinstance being used to render your texts. In order for this change to go into effect you'll have to rebuild yourBlotterinstance by callingblotter.update(). -
addText
blotter.addText(text)Alias method for
blotter.addTexts(text), see below. -
addTexts
blotter.addTexts(texts)Adds one or more
Textobjects to be rendered by your instance ofBlotter. In order for this change to go into effect you'll have to rebuild yourBlotterinstance by callingblotter.update(). -
removeText
blotter.removeText(text)Alias method for
blotter.removeTexts(text), see below. -
removeTexts
blotter.removeTexts(texts)Removes one or more
Textobjects from being rendered by your instance ofBlotter. In order for this change to go into effect you'll have to rebuild yourBlotterinstance by callingblotter.update(). -
forText
blotter.forText(text)Returns the
RenderScopefor the givenTextobject, orundefinedif the text is not known to this instance. -
boundsForText
blotter.boundsForText(text)Returns an object containing the width (
w), height (h), x-offset (x), and y-offset (y) of the givenTextobject in the back buffer theBlotterinstance uses to render the material's effect collectively for all texts in the instance. These values, especially the x-offset and y-offset, will likely be of little use to you. -
on
blotter.on(event, listener)Subscribes to one of the instance's events and returns a function that unsubscribes. Every emitter in Blotter (
Material,Text,RenderScope,UniformInterface) has the sameon,off, andonceshape.const off = blotter.on("render", () => { // called after every frame }); off(); // unsubscribe -
events
"ready" | "update" | "render"readyfires once after the first build,updateafter each later rebuild, andrenderafter every frame.
Material
Blotter Materials describe how your texts will be rendered and provide the interface
through which you can control uniforms to introduce variety in the
characteristics of any effect.
As the Material class is the base class for all Blotter
Materials, using this class on its own will simply render your
texts according to their font and style properties. See the documentation for specific
Materials for further details on their individual usages, and
custom materials below for writing your own.
-
construction
const material = new Material({ mainImage, uniforms });Create a new instance of
Material. Both options are optional.-
mainImage
options.mainImageA Shadertoy-style
GLSLfragment body. When omitted, the material draws the text unchanged. -
uniforms
options.uniformsA
UniformMapof the uniforms yourmainImagereads, each with atypeand an initialvalue.
-
-
mainImage
material.mainImageFor every Blotter Material, there is an underlying
GLSLfragment shader that describes how the individual Material's effect will be rendered. Thematerial.mainImageproperty returns the shader'sGLSLin string form. Assigning a falsy value restores the default passthrough shader. Setting this property after initialization requires that the shader be rebuilt by callingmaterial.update().The shader must define
mainImageand sample the text withtextTexture(uv)rather thantexture2D:void mainImage( out vec4 mainImage, in vec2 fragCoord ) { // fragCoord is in pixels; uResolution is the size of this text. vec2 uv = fragCoord / uResolution; mainImage = textTexture(uv); } -
uniforms
material.uniformsThe fundamental interface for all effects in Blotter. Reading this property gives you a map of
UniformInterfaceobjects, each having atypeand a livevalue. To manipulate any effect, simply set thevalueof the uniform you wish to change; the running shader picks it up on the next frame.Assigning a whole
UniformMapreplaces every uniform. Because new uniform names need new shader declarations, callmaterial.update()afterwards.material.uniforms = { uAmount: { type: "1f", value: 0.05 }, }; material.update();Listed below are the four uniform types available in Blotter.
-
"1f"
For
"1f"type uniforms, values should be set using floating point values:material.uniforms.myUniform1.value = 0.5; -
"2f"
For
"2f"type uniforms, values should be set using an array of two floating point values:material.uniforms.myUniform2.value = [0.5, 0.5]; -
"3f"
For
"3f"type uniforms, values should be set using an array of three floating point values:material.uniforms.myUniform3.value = [0.5, 0.5, 0.5]; -
"4f"
For
"4f"type uniforms, values should be set using an array of four floating point values:material.uniforms.myUniform4.value = [0.5, 0.5, 0.5, 0.5];
As stated, every Blotter Material will provide its own uniforms unique to the characteristics of its own effect. However, there are a handful of uniforms on which all Blotter Materials rely. These are always present, and always overwrite a same-named uniform of your own.
-
uResolution
The resolution of an individual text within the mapping material being rendered by your material. Type
"2f". You should never set the value of this uniform yourself. -
uGlobalTime
The global time in seconds. Type
"1f". You should never set the value of this uniform yourself. -
uTimeDelta
The render time in seconds. Type
"1f". You should never set the value of this uniform yourself. -
uBlendColor
The base color against which all blending should occur within an effect. Type
"4f". The default value for this uniform is white, or[1.0, 1.0, 1.0, 1.0], where each index in the array represents an R, G, B, and A value respectively in a 0.0 to 1.0 range.This uniform is important for effects that sample the area around your texts for blending purposes, such as for the RGB splitting that occurs in the ChannelSplitMaterial, and you should set it to match the RGBA color that will be the background for any of your texts.
-
uPixelRatio
The pixel ratio of the user's device. Type
"1f". You should never set the value of this uniform yourself.
-
-
update
material.update()Tells every
Blotterinstance using this material that it changed structurally, for example after assigning a newmainImageor a newuniformsmap, so they rebuild their shader. Plainvaluewrites on a uniform do not need this. -
events
"update" | "update:uniform"updatefires aftermaterial.update();update:uniformfires with the uniform's name whenever one of its values changes.
ShaderMaterial
A convenience subclass of Material for a one-off fragment shader, so the
shader source comes first and nothing has to be subclassed.
-
construction
const material = new ShaderMaterial(mainImage, { uniforms });Equivalent to
new Material({ mainImage, uniforms }). Everything documented forMaterialapplies.
Text
For each string of text you wish to render with a given Material, you should create an
instance of Text.
-
construction
const text = new Text(value, properties);Create a new instance of
Textwherevalueis a string of text1 andpropertiesis an optional styles object for applying styles to your texts prior to them being rendered with your Material. The styles object takes any of the following optional parameters.-
family
properties.familyA string representing the font family to be applied to the text. The default value is
"sans-serif". -
size
properties.sizeA number representing the size of the rendered text in pixels. The default value is
12. -
leading
properties.leadingThe leading or line-height of the rendered text: a unitless multiplier, or a string in
"px"or"%". The default value is1.5. You should try to keep this number above 1.0, as Blotter bases text positions within the text atlas on computed text heights, and leading values that are lower than the text size they correspond with can cause texts to overflow into adjacent canvases. -
fill
properties.fillA string representing the color of the rendered text. The default value is
"#000". -
style
properties.styleA string representing the style of the rendered text, such as
"normal"or"italic". The default value is"normal". -
weight
properties.weightA number or string representing the weight of the rendered text. The default value is
400. -
padding
properties.paddingA number representing the padding around the rendered text. The default value is
0. -
paddingTop
properties.paddingTopA number representing the padding above the rendered text. The default value is
0. -
paddingRight
properties.paddingRightA number representing the padding to the right of the rendered text. The default value is
0. -
paddingBottom
properties.paddingBottomA number representing the padding below the rendered text. The default value is
0. -
paddingLeft
properties.paddingLeftA number representing the padding to the left of the rendered text. The default value is
0.
-
-
id
text.idA unique, read-only identifier for the instance.
-
value
text.valueThe string of text for the instance. Assigning a new value after initialization rebuilds every
Blotterinstance rendering this text automatically. -
properties
text.propertiesThe styles object representing the style properties being applied to your string of text. Reading it returns the full, defaulted set. Assigning a partial object replaces the properties (merged over the defaults, not over the previous values) and rebuilds automatically.
text.value = "Goodbye"; // rebuilds automatically text.properties = { size: 90 }; // ditto (merged over the defaults) -
update
text.update()Notifies observers that this text changed.
valueandpropertieswrites call it automatically; call it directly after bulk edits. -
events
"update"Fires whenever the text's value or properties change.
RenderScope
Blotter RenderScopes are the primary interface for interacting with your texts on an
individual basis. When you render one or more texts inside an instance of
Blotter, the instance will create the scopes for you automatically, and you
can use them to perform a number of critical actions, such as placing your rendered texts
within DOM elements and setting uniform values specific to individual texts while
rendering texts collectively in a single material.
-
obtainment
const scope = blotter.forText(text); // RenderScope | undefinedYou will never create
RenderScopeobjects directly. Instead, you should obtain them from your instance of blotter by callingblotter.forText(text)wheretextis an instance ofTextknown to yourblotterobject. -
text
scope.textThe instance of
Textfor the scope. -
domElement
scope.domElementThe DOM element for the given scope. This will return a HiDPI canvas with a
2dcontext containing an up-to-date rendering of your text. You should place this element where you want the text for the scope to appear on the page. You may access the element directly through this property, or append it to another DOM element using the convenience method,scope.appendTo(domElement). -
material
scope.materialThe primary interface for interacting with the Material being rendered by your
Blotterinstance in a manner specific to an individual text. This is critically important for updating uniforms in a non-global way. For example, if you are rendering several texts using a single Material, and want to have one of those texts update its visual appearance on a mouseover event without changing the other texts, you would want to access the uniforms for the Material through the scope, and not on the Material's instance directly.material.uniforms.uOffset.value = 0.1; // every text const scope = blotter.forText(text); if (scope) { scope.material.uniforms.uOffset.value = 0.2; // this text only }Per-text uniforms only exist after the first build, so read them after
await blotter.ready. -
playing
scope.playingA boolean value indicating whether or not the scope will actively be rendered by the
Blotterinstance. On initialization, this value is set to the value of theBlotterinstance'sautoplayproperty. -
timeDelta
scope.timeDeltaThe scope's render time in seconds.
-
frameCount
scope.frameCountThe number of times the scope has been rendered.
-
play
scope.play()Tells the scope to begin or resume being rendered by the instance of
Blotter. -
pause
scope.pause()Tells the scope to pause being rendered by the instance of
Blotter. -
appendTo
scope.appendTo(element)A convenience method for appending the scope's
domElementto the page. Returns the scope, so it can be chained. Pointer events are only wired up once the canvas has been appended this way. -
events
"ready" | "update" | "render" | "update:uniform" | "mousedown" | "mouseup" | "mousemove" | "mouseenter" | "mouseleave"readyandupdatemirror theBlotterevents for this text;renderreceives the frame count. The pointer events receive the position normalised to the canvas, as{ x, y }from0to1, withygrowing downwards as in the DOM.const scope = blotter.forText(text); if (scope) { scope.on("mouseenter", ({ x, y }) => { scope.material.uniforms.uDodgePosition.value = [x, 1 - y]; }); }
Uniforms
Uniforms are declared as plain descriptors and exposed as live interfaces. The descriptor types are exported for TypeScript users.
type UniformType = "1f" | "2f" | "3f" | "4f";
type Vec2 = [number, number];
type Vec3 = [number, number, number];
type Vec4 = [number, number, number, number];
type UniformDescriptor =
| { type: "1f"; value: number }
| { type: "2f"; value: Vec2 }
| { type: "3f"; value: Vec3 }
| { type: "4f"; value: Vec4 };
type UniformMap = Record<string, UniformDescriptor>; -
UniformInterface
material.uniforms.uNameThe live handle for one uniform, found on
material.uniformsandscope.material.uniforms.-
type
uniform.typeThe
UniformType. Read-only. -
value
uniform.valueThe current value. Writes are validated against the type (a wrong shape is logged and ignored) and emit
update, which is how the change reaches the running shader. -
toDescriptor
uniform.toDescriptor()Returns a plain
{ type, value }UniformDescriptor.
-
shaders
GLSL helper snippets are exported as strings from the shaders
namespace, ready to be interpolated into a mainImage:
blending, blinnPhongSpecular, easing,
gamma, inf, lineMath, map,
noise, noise2d, noise3d, noise4d,
pi, and random. The blending helpers
(normalBlend and friends) are always available to every material.
import { Material, shaders } from "blotter.ts";
const material = new Material({
mainImage: /* glsl */ `
${shaders.noise3d}
void mainImage( out vec4 mainImage, in vec2 fragCoord ) {
vec2 uv = fragCoord / uResolution;
uv.y += snoise(vec3(uv * 4.0, uGlobalTime)) * uAmount;
mainImage = textTexture(uv);
}
`,
uniforms: {
uAmount: { type: "1f", value: 0.05 },
},
}); Utilities
-
isWebGLSupported
isWebGLSupported()Returns
truewhen the browser can create a WebGL context. TheBlotterconstructor throws when it cannot, so check this first when you want to fall back to plain text. -
pixelRatio
pixelRatio()The device pixel ratio, or
1outside a browser. This is whatBlotteruses when noratiooption is given. -
filterTexts
filterTexts(texts)Coerces a
Text, an array of texts, or an array-like intoText[], warning on and dropping anything that is not aText.import { filterTexts, isWebGLSupported, pixelRatio } from "blotter.ts"; if (isWebGLSupported()) { const blotter = new Blotter(material, { ratio: pixelRatio() }); }
Custom materials
A material is a Shadertoy-style mainImage fragment function. Use
textTexture(uv) instead of texture2D to sample the text, read
your own uniforms by name, and interpolate any helpers you need from
shaders. Every material also receives uResolution,
uGlobalTime, uTimeDelta, uBlendColor, and
uPixelRatio.
import { Material, shaders } from "blotter.ts";
const material = new Material({
mainImage: /* glsl */ `
${shaders.noise3d}
void mainImage( out vec4 mainImage, in vec2 fragCoord ) {
vec2 uv = fragCoord / uResolution;
uv.y += snoise(vec3(uv * 4.0, uGlobalTime)) * uAmount;
mainImage = textTexture(uv);
}
`,
uniforms: {
uAmount: { type: "1f", value: 0.05 },
},
});
Or subclass Material, which is how the bundled effects in
blotter.ts/materials are written:
import { Material, shaders } from "blotter.ts";
const mainImage = /* glsl */ `
${shaders.noise3d}
void mainImage( out vec4 mainImage, in vec2 fragCoord ) {
vec2 uv = fragCoord / uResolution;
uv.y += snoise(vec3(uv * 4.0, uGlobalTime)) * uAmount;
mainImage = textTexture(uv);
}
`;
export class WobbleMaterial extends Material {
constructor() {
super({ mainImage, uniforms: { uAmount: { type: "1f", value: 0.05 } } });
}
}