Created
February 23, 2023 14:25
-
-
Save fabiogiolito/b5aac76c4d1ae0eb23012be7befcd73f to your computer and use it in GitHub Desktop.
A Svelte component that types text like ChatGPT.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script> | |
import { onMount } from "svelte"; | |
// Text to display | |
export let text = ""; | |
// <Typerwiter paused … /> to start paused | |
export let paused = false; | |
// <Typerwiter cursor … /> to show blinking cursor | |
export let cursor = false; | |
// <Typerwiter debug … /> to show debug buttons and data | |
export let debug = false; | |
// More options | |
export let delayStart = 1000; // Delay in ms before words start appearing | |
export let delayRange = [250, 1000]; // Delay in ms between each update [min, max] | |
export let wordRange = [3, 8]; // How many words at each update [min, max] | |
// Local state | |
let timeout = null; // Store timeout so we can pause/resume/end | |
let remainingWords = text.trim().split(" "); // Split text in words | |
let visibleText = ""; // We'll add words here to be displayed | |
// ============================== | |
// Functions | |
// ------------------------------ | |
// Start showing words | |
function handlePlay() { | |
paused = false; // unpause | |
// Check for initial delay | |
if (delayStart) { | |
timeout = setTimeout(handlePlay, delayStart); | |
delayStart = 0; | |
return | |
} | |
// No initial delay, good to go | |
// Get some random duration based on delayRange | |
const delay = Math.floor(Math.random() * (delayRange[1] - delayRange[0] + 1)) + delayRange[0]; | |
// Set timeout to add a new block of words | |
timeout = setTimeout(() => { | |
// Reached the end | |
if (!remainingWords.length) { | |
handlePause(); | |
return; | |
} | |
// Get some random number of characters in given range | |
const length = Math.floor(Math.random() * (wordRange[1] - wordRange[0] + 1)) + wordRange[0]; | |
// Append that number of words to visible text | |
visibleText += remainingWords.slice(0, length).join(" ") + " "; | |
// Remove that number of words from remaining words | |
remainingWords = remainingWords.slice(length); | |
handlePlay(); | |
}, delay); | |
} | |
// ------------------------------ | |
// Stop showing words | |
function handlePause() { | |
paused = true; | |
clearTimeout(timeout); | |
} | |
// ------------------------------ | |
// Show entire text | |
function handleFinish() { | |
handlePause(); | |
remainingWords = ""; | |
visibleText = text; | |
} | |
// ------------------------------ | |
// Start over | |
function handleRestart() { | |
handlePause(); | |
remainingWords = text.trim().split(" "); // Split text in words | |
visibleText = ""; // We'll add words here to be displayed | |
handlePlay(); | |
} | |
// ------------------------------ | |
// Component mounted | |
onMount(() => { | |
// Start on mount unless paused | |
if (!paused) handlePlay(); | |
}); | |
</script> | |
{#if debug} | |
<div> | |
<button disabled={paused == true || !remainingWords.length} on:click={handlePause}>Pause</button> | |
<button disabled={paused == false || !remainingWords.length} on:click={handlePlay}>Resume</button> | |
<button disabled={!remainingWords.length} on:click={handleFinish}>Finish</button> | |
<button on:click={handleRestart}>Restart</button> | |
<p>Remaining words: {remainingWords.length}</p> | |
</div> | |
{/if} | |
<p class:with-cursor={cursor && !paused && remainingWords.length}>{visibleText}</p> | |
<style> | |
div { | |
border-bottom: 1px solid; | |
margin-bottom: 1em; | |
padding-bottom: 1em; | |
} | |
button { | |
border: 1px solid; | |
padding: 0.25em 0.5em; | |
border-radius: 0.25em; | |
} | |
button:disabled { | |
opacity: 0.5; | |
} | |
p { white-space: pre-wrap;} | |
.with-cursor:after { | |
content: ''; | |
display: inline-block; | |
vertical-align: middle; | |
position: relative; | |
top: -2px; | |
width: 0.5em; | |
height: 1.2em; | |
margin: -1.2em 0; | |
background-color: currentColor; | |
opacity: 0.5; | |
animation: blink-animation 1s steps(5, start) infinite; | |
} | |
@keyframes blink-animation { | |
to { visibility: hidden; } | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment