@memotrode/speech-bubbler
v0.1.0
Published
Comic book style speech-bubble and dialogue-conversation Vue components.
Maintainers
Readme
@memotrode/speech-bubbler
Responsive speech-bubble and dialogue-conversation logic and Vue components, styled with typed-in-place text, directional tails, multiple tails, and timed or input-driven multi-part, multi-bubble conversations.
@memotrode/speech-bubbler/core- framework-agnostic logic (markup parsing, typewriter timing, conversation sequencing, tail placement).@memotrode/speech-bubbler/vue- Vue 3 components (SpeechBubble,SpeechStage) and composables (useTypewriter,useSpeakerTurns, ...) built on top ofcore.
Usage
<script setup lang="ts">
import { SpeechStage } from "@memotrode/speech-bubbler/vue";
import "@memotrode/speech-bubbler/style.css";
import type { SpeakerTurn } from "@memotrode/speech-bubbler/vue";
const speakerAnchor = useTemplateRef("speakerAnchor");
const turns: SpeakerTurn[] = [
{
id: "1",
speakerIds: ["speaker"],
text: "Hey! Ready to go?",
speaker: { name: "Speaker", avatarUrl: "/avatars/speaker.png" },
typewriter: { enabled: true, charsPerSecond: 30 },
advance: { mode: "input" },
},
{
id: "2",
speakerIds: ["speaker"],
text: "{shout}Let's go!{/shout}",
advance: { mode: "auto", delayMs: 1500 },
},
];
</script>
<template>
<div ref="speakerAnchor" class="avatar" />
<SpeechStage :turns="turns" :anchors="{ speaker: speakerAnchor }" @complete="onDone" />
</template>SpeechStage positions bubbles automatically around their speaker(s) and aims a
tapered tail at it live - pass one DOM ref per speakerId via anchors. A single
bubble can also be rendered directly with <SpeechBubble> for simple prompt-style
text without conversation sequencing.
Markup
Bubble text is a small directive language: {tag}...{/tag} wraps a run of text,
and directives can nest. It's
parsed by parseExpressiveText (core) into styled TextSegments, which the
typewriter reveals incrementally and SpeechBubble renders.
Text style
| Tag | Effect |
| ---------------------------- | --------- |
| {bold}...{/bold} | Bold |
| {italic}...{/italic} | Italic |
| {underline}...{/underline} | Underline |
These three stack independently as a set - {bold}{italic}hi{/italic}{/bold} is both.
Size and speed
{size_x:N}/{size:N} scale a run's font size - size_x is a multiplier of the
bubble's base size, size is an absolute em value. {speed_x:N}/{speed:N} scale
typing rate the same way (multiplier of baseline, or absolute chars/sec). Unlike
bold/italic/underline, nested size/speed tags do not stack.
Size has named step aliases: {xs} {sm} {md} {lg} {xl} {2xl} {3xl}
(0.3x-2.1x). Speed impacts the typewriter speed and has {slow} (0.5x), {moderate} (1x), {fast} (2x), and
{instant} (99 chars/sec absolute).
Semantic color
Color tags tint a run of text - by default just a color change, no other styling.
| Tag | Default color |
| -------------------------- | ------------- |
| {danger}...{/danger} | red |
| {warning}...{/warning} | amber |
| {info}...{/info} | blue |
| {notice}...{/notice} | blue |
| {success}...{/success} | green |
| {positive}...{/positive} | green |
| {negative}...{/negative} | red |
| {quote}...{/quote} | gray |
Each renders as an sb-color-{name} class driven by a --sb-color-{name} CSS
custom property (e.g. --sb-color-danger) - override these on :root or a
bubble's own element to customize them.
Animation
{animate:id} (sugar for {animate_letters:id}), {animate_letters:id}, and
{animate_words:id} animate a run per-letter or per-word. id is shake or wave.
Optional comma-separated args set intensity and speed (both default to
1): {animate:shake,2,3}. {/animate} and {/animate_letters}. Animation only renders in the bubble's
typewriter.mode: "expressive" (see below) - "standard" mode parses these
directives but doesn't animate, since it renders flat per-segment spans rather than
one span per character (more performant for longer text).
Expression aliases
A few tags combine several primitives into one named expression:
| Tag | Expands to |
| ----------- | ---------------------------------------------- |
| {whisper} | slow + small |
| {exclaim} | bold + large |
| {shout} | bold + fast + extra-large |
| {yell} | shaking (mild) + bold + italic + fast + 2xl |
| {whimper} | shaking (mild, slow) + slow + small |
| {shriek} | shaking (intense) + bold + italic + fast + 2xl |
Components
<SpeechBubble>
Renders a single bubble: typed-in-place text (or a multi-part array of "paragraphs" shown one
at a time within the same bubble), an optional speaker name/avatar, and zero-to-many tails aimed
at target points. Sizing is "dynamic" (grows live as text reveals), "auto"
(appears immediately as the size of the fully typed text), or a fixed
{ width, height }. See BubbleConfig for the full prop set.
<SpeechStage>
Drives a multi-speaker script (SpeakerTurn[]) end-to-end: sequences turns, resolves
each turn's on-screen position relative to its speaker(s)' anchor element(s), and
aims each bubble's tail(s) live as anchors move. Each turn can target multiple
speakerIds at once (e.g. a group answering in unison). Turns advance on a timer ({ mode: "auto", delayMs? } - omitting
delayMs falls back to a reading-time estimate) or on demand ({ mode: "input" },
via the exposed advance() or the bubble slot's requestAdvance).
position, minGap, tailDirection/tailAngle, and tailBaseWidth control where
a turn's bubble hovers and how its tail is aimed - each has a stage-level default,
overridable per turn. fixed: true freezes a turn's bubble position once resolved
(useful when its anchor moves often) while its tail
keeps tracking the live anchor.
Development
- Install dependencies:
vp install- Run the unit tests:
vp test- Build the library:
vp pack