Text
The Text element provides advanced text layout, measurement, and typography features for complex text rendering.
Access
const draw = (K: KlintContext) => {
// Access the Text element
const size = K.Text.findTextSize("Hello World", 300);
const letters = K.Text.splitTo("Text", "letters");
}
Measurement Functions
getTextMetrics(text)
getTextMetrics(text: string) => { width: number, height: number, baseline: number }
Returns detailed metrics for text rendering.
const metrics = K.Text.getTextMetrics("Hello World")
console.log(metrics.width) // Text width in pixels
console.log(metrics.height) // Text height in pixels
console.log(metrics.baseline) // Baseline offset
textBounds(text)
textBounds(text: string) => { x: number, y: number, width: number, height: number }
Returns the bounding box for text rendering.
const bounds = K.Text.textBounds("Sample Text")
K.strokeColor("red")
K.strokeWidth(1)
K.rectangle(bounds.x, bounds.y, bounds.width, bounds.height)
findTextSize(text, distance, estimate?, direction?)
findTextSize(text: string, dist: number, estimate?: number, direction?: "x" | "y") => number
Finds the optimal text size to fit within specified dimensions using binary search.
// Find size to fit text in 300px width
const size = K.Text.findTextSize("Long text string", 300, 50, "x")
K.textSize(size)
K.text("Long text string", 100, 200)
// Find size to fit text in 100px height
const vSize = K.Text.findTextSize("Tall Text", 100, 72, "y")
K.textSize(vSize)
K.text("Tall Text", 200, 200)
Text Layout Functions
splitTo(text, kind, options?)
splitTo(
text: string,
kind: "letters" | "words" | "lines" | "all",
options?: {
maxWidth?: number,
lineSpacing?: number,
letterSpacing?: number,
wordSpacing?: number
}
) => Array<{
char: string,
x: number,
y: number,
metrics: { width: number, height: number, baseline: number },
letterIndex?: number,
wordIndex?: number,
lineIndex?: number
}>
Splits text into individual components with positioning data for advanced layout.
// Split into letters for animation
const letters = K.Text.splitTo("ANIMATE", "letters")
letters.forEach((letter, i) => {
const offset = Math.sin(K.time * 0.005 + i * 0.5) * 20
K.fillColor(`hsl(${i * 40}, 70%, 60%)`)
K.text(letter.char, letter.x, letter.y + offset)
})
// Split into words with spacing
const words = K.Text.splitTo("Split these words", "words", {
wordSpacing: 20
})
words.forEach((letter, i) => {
if (letter.char !== " ") {
K.fillColor("white")
K.text(letter.char, letter.x + 100, letter.y + 300)
}
})
// Multi-line text with custom spacing
const lines = K.Text.splitTo("Line one\nLine two\nLine three", "lines", {
lineSpacing: 30
})
lines.forEach((letter) => {
K.fillColor("cyan")
K.text(letter.char, letter.x + 200, letter.y + 400)
})
// Get all metadata for complex layouts
const allData = K.Text.splitTo("Complex layout", "all")
allData.forEach((letter) => {
const hue = (letter.letterIndex || 0) * 20
K.fillColor(`hsl(${hue}, 60%, 70%)`)
K.text(letter.char, letter.x + 300, letter.y + 500)
})
circularText(text, radius?, fill?, offset?, arc?)
circularText(
text: string,
radius?: number,
fill?: "fill" | "kerned" | "words",
offset?: number,
arc?: number
) => void
Renders text along a circular path with different layout modes.
// Basic circular text
K.Text.circularText("CIRCULAR TEXT", 100)
// Kerned (proper character spacing)
K.Text.circularText("BETTER SPACING", 150, "kerned", Math.PI/4)
// Words distributed evenly
K.Text.circularText("WORD BASED LAYOUT", 200, "words", 0, Math.PI)
// Partial circle with offset
K.Text.circularText("PARTIAL ARC", 120, "fill", K.time * 0.01, Math.PI * 1.5)
Animation Examples
Letter-by-Letter Animation
const draw = (K: KlintContext) => {
K.background("#222")
const text = "STAGGERED REVEAL"
const letters = K.Text.splitTo(text, "letters")
K.textSize(48)
K.textAlign("center", "middle")
letters.forEach((letter, i) => {
// Staggered fade-in
const delay = i * 0.1
const progress = Math.max(0, (K.time * 0.003 - delay) % 2)
const alpha = K.Easing.out(Math.min(1, progress))
if (letter.char !== " ") {
K.fillColor(`rgba(255, 255, 255, ${alpha})`)
// Add bounce effect
const bounce = Math.sin(progress * Math.PI) * 10
K.text(letter.char, letter.x + K.width/2, letter.y + K.height/2 - bounce)
}
})
}
Word Wave Effect
const draw = (K: KlintContext) => {
K.background("#111")
const sentence = "WAVES OF TEXT"
const letters = K.Text.splitTo(sentence, "all", { wordSpacing: 40 })
K.textSize(32)
K.textAlign("center", "middle")
letters.forEach((letter) => {
if (letter.char !== " ") {
const wordOffset = (letter.wordIndex || 0) * 0.8
const letterOffset = (letter.letterIndex || 0) * 0.2
const wave = Math.sin(K.time * 0.004 + wordOffset + letterOffset) * 30
const hue = ((letter.wordIndex || 0) * 60) % 360
K.fillColor(`hsl(${hue}, 80%, 70%)`)
K.text(letter.char, letter.x + K.width/2, letter.y + K.height/2 + wave)
}
})
}
Responsive Text Sizing
const draw = (K: KlintContext) => {
K.background("#333")
// Text that always fits the canvas width
const availableWidth = K.width - 100
const dynamicText = "RESPONSIVE TEXT SIZING"
const optimalSize = K.Text.findTextSize(dynamicText, availableWidth, 72, "x")
K.textSize(optimalSize)
K.textAlign("center", "middle")
K.fillColor("yellow")
K.text(dynamicText, K.width/2, K.height/2)
// Show bounds for debugging
const bounds = K.Text.textBounds(dynamicText)
K.strokeColor("red")
K.strokeWidth(2)
K.rectangle(
K.width/2 + bounds.x,
K.height/2 + bounds.y,
bounds.width,
bounds.height
)
}
Circular Text Animation
const draw = (K: KlintContext) => {
K.background("#000")
K.push()
K.translate(K.width/2, K.height/2)
// Animated circular text
K.textSize(24)
K.fillColor("cyan")
// Rotating text with changing radius
const radius = 100 + Math.sin(K.time * 0.002) * 30
const offset = K.time * 0.005
K.Text.circularText("SPINNING AROUND", radius, "kerned", offset)
// Multiple rings
K.fillColor("magenta")
K.Text.circularText("INNER RING", 60, "fill", -offset * 1.5, Math.PI)
K.fillColor("yellow")
K.Text.circularText("OUTER SPACE", 180, "words", offset * 0.7, Math.PI * 1.8)
K.pop()
}
Multi-line Layout
const draw = (K: KlintContext) => {
K.background("#444")
const poem = `Typography is the craft
of endowing human language
with a durable visual form`
const lines = K.Text.splitTo(poem, "lines", {
lineSpacing: 20
})
K.textSize(28)
K.textAlign("center", "top")
lines.forEach((letter, i) => {
// Color-code each line
const lineColor = letter.lineIndex === 0 ? "lightblue" :
letter.lineIndex === 1 ? "lightgreen" : "lightcoral"
// Add typewriter effect
const lineProgress = (K.time * 0.002 - (letter.lineIndex || 0) * 1.5) % 4
const charIndex = letter.letterIndex || 0
const shouldShow = charIndex < lineProgress * 20
if (shouldShow) {
K.fillColor(lineColor)
K.text(letter.char, letter.x + K.width/2, letter.y + 200)
}
})
}
Layout Options
The splitTo function accepts these options:
- maxWidth: Maximum line width for automatic wrapping
- lineSpacing: Additional space between lines (pixels)
- letterSpacing: Additional space between letters (pixels)
- wordSpacing: Additional space between words (pixels)
CircularText Fill Modes
- "fill": Evenly distributes characters around the circle
- "kerned": Uses actual character widths for natural spacing
- "words": Distributes words evenly, maintaining word integrity
Best Practices
- Use
findTextSize()for responsive text that must fit specific dimensions - Leverage
splitTo()for character-level animations and effects - Use
textBounds()for precise positioning and collision detection circularText()with "kerned" mode provides the most natural appearance- Combine with
K.TimeandK.Easingfor smooth text animations - Cache
splitTo()results when text doesn't change to improve performance
Notes
- All positioning respects current
textAlignandtextBaselinesettings - Text metrics include actual character dimensions, not just CSS font metrics
findTextSize()uses binary search for efficient size calculation (max 16 iterations)- Circular text automatically handles rotation and positioning
- Letter spacing and word spacing are additive to default browser spacing
- Performance scales with text length - consider caching for static text