Skip to content

Commit

Permalink
typewritten-text | Begin transcribing the typewritten-text component …
Browse files Browse the repository at this point in the history
…to this repository
  • Loading branch information
Auroratide committed Feb 10, 2024
1 parent 6efaf3e commit aa9723b
Show file tree
Hide file tree
Showing 17 changed files with 1,153 additions and 2 deletions.
2 changes: 2 additions & 0 deletions components/typewritten-text/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
lib
2 changes: 2 additions & 0 deletions components/typewritten-text/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/src
/test
5 changes: 5 additions & 0 deletions components/typewritten-text/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Copyright 2024 Timothy Foster

Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
244 changes: 244 additions & 0 deletions components/typewritten-text/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
# The typewritten-text Element

<p hidden><strong><a href="https://auroratide.github.io/web-components/typewritten-text">View this page with live demos!</a></strong></p>

The `typewritten-text` element represents text that should be typed out one letter at a time when displayed.

![Text is automatically typed out one letter at a time](./demo.gif)

<!--DEMO
<wc-demo id="main-demo">
<p>This <typewritten-text paused>typewriter effect</typewritten-text> is achieved using <typewritten-text paused>custom elements!</typewritten-text></p>
<button class="run">Run</button>
</wc-demo>
/DEMO-->

```html
This <typewritten-text paused>typewriter effect</typewritten-text> is achieved
using <typewritten-text paused>custom elements!</typewritten-text>
```

## Installation

You can import through CDN:

```html
<link rel="stylesheet" href="https://unpkg.com/@auroratide/typewritten-text/lib/style.css" />
<script type="module" src="https://unpkg.com/@auroratide/typewritten/lib/define.js"></script>
```

Or, you may install through [NPM](https://www.npmjs.com/package/@auroratide/typewritten-text) and include it as part of your build process:

```
$ npm i @auroratide/typewritten-text
```

```javascript
import '@auroratide/typewritten-text/lib/style.css'
import '@auroratide/typewritten-text/lib/define.js'
```

**Note**: How you import into your project depends on its configuration. The `style.css` file should be imported with your root CSS, and the `define.js` file should imported with your root Javascript.


## Usage

`typewritten-text` is an **inline markup element** that you can use in your HTML document.

```html
Some <typewritten-text>text to type out!</typewritten-text>
```

Since this is Just HTML<sup>TM</sup>, you can use `typewritten-text` with other markup tags:

```html
This works with <typewritten-text paused>
<strong>other</strong>
<em>markdown</em>
<span class="special">elements</span>
</typewritten-text> as well!
```

<!--DEMO
<wc-demo id="markup-demo">
<p>This works with <typewritten-text paused> <strong>other</strong> <em>markdown</em> <span class="special">elements</span> </typewritten-text> as well!</p>
<button class="run">Run</button>
</wc-demo>
<style>
.special {
font-size: 1.25em;
color: oklch(45% 0.1 20);
}
</style>
/DEMO-->

**Note:** `typewritten-text` has text-level semantics, meaning it can contain anything that a `span` can contain. See [Phrasing Content](https://html.spec.whatwg.org/#phrasing-content-2).

### Repeat Indefinitely

This types and backspaces the text on a loop.

```html
<p>Some <typewritten-text repeat>text to type out!</typewritten-text></p>
```

### Adjust Timing

The time provided is number of milliseconds between each letter.

```html
<p>Some <typewritten-text letter-interval="400">text to type out!</typewritten-text></p>
```

The `phrase-interval` is the time between when the text is typed out and when it starts to be removed during a repetition loop.

```html
<p>Some <typewritten-text repeat phrase-interval="2000">text to type out!</typewritten-text></p>
```

### Start Paused

This will start paused until invoked by **javascript**.

```html
<p>Some <typewritten-text paused>text to type out!</typewritten-text></p>
```

### All Attributes

| Attribute | Default | Description |
| ------------- | --------- | ------------- |
| `repeat` | - | Whether the text should type itself repeatedly on a loop |
| `letter-interval` | 100 | Time between each letter in milliseconds |
| `phrase-interval` | 1000 | Time between completion and restart during a repeat loop in milliseconds |
| `paused` | - | Whether the animation should start paused |

## Style API

Since `typewritten-text` is Just HTML<sup>TM</sup>, you can style it the same way you style any HTML tag.

```css
typewritten-text {
color: red;
}
```

**Note**: Depending on what you want to do, you may run into some [Implementation Gotchas](#implementation-gotchas).

### Cursor

The blinking cursor can be customized with either CSS variables or directly via selectors.

| Variable | Default | Description |
| ------------- | --------- | ------------- |
| `--typewritten-text_cursor-width` | 0.125em | How wide the cursor is |
| `--typewritten-text_cursor-style` | solid | Whether the cursor is solid, dashed, dotted, etc; can be any border-style value |
| `--typewritten-text_cursor-color` | currentColor | Color of the cursor |
| `--typewriten-text_cursor-interval` | 700ms | The duration of the blink animation |

The cursor can be arbitrarily customized with the following CSS selectors:

```css
.typewritten-text_character::after,
.typewritten-text_start::after { }
```

The `*_start` selector represents the start of the text and can be used to style the initial cursor differently than the cursor-in-motion. For example, to hide the cursor while the animation is paused and yet show it at the start, you can do:

```css
typewritten-text[paused] .typewritten-text_character::after {
visibility: hidden;
}
```

## Javascript API

The element exposes some useful methods to enable custom animation. Once you have obtained a reference to a `TypewrittenText` element:

```js
const elem = document.querySelector('typewritten-text')
```

You can use the following methods:

| Method | Description |
| ------------- | ------------- |
| `start()` | Start the animation cycle if it is currently paused |
| `pause()` | Pause the animation cycle if it is currently running |
| `typeNext()` | Manually type the next character |
| `backspace()` | Manually remove one character |
| `tick()` | Run one frame of the animation; only works if not paused |
| `forceTick()` | Run one frame of the animation regardless of paused state |
| `reverse()` | Reverse the direction of the animation |
| `reset()` | Completely resets the element and animation; may be useful if the content within the element is dynamic |

### Properties

Each attribute can be accessed as a Javascript property.

* `elem.repeat`
* `elem.paused`
* `elem.letterInterval`
* `elem.phraseInterval`

One additional property is provided:

* `elem.length`: The total number of typeable characters

### Events

The `typewritten-text` element dispatches the following events:

| Name | When Triggered |
| ------------- | ------------- |
| `typewritten-text:nextchar` | Anytime a character is typed into view |
| `typewritten-text:prevchar` | Anytime a character is removed from view |
| `typewritten-text:phrasetyped` | When the full phrase becomes fully typed |
| `typewritten-text:phraseremoved` | When the full phrase becomes untyped |
| `typewritten-text:started` | When the animation is started |
| `typewritten-text:paused` | When the animation is paused |

### Element Class

The element interface can be accessed in javascript as well.

```js
import { TypewrittenText } from '@auroratide/typewritten-text'
```

## Accessibility

This custom element is built with accessibility in mind!

* The `typewritten-text` element always represents its textual content regardless of visibility state. Screenreaders should read the text in its entirety.
* The textual content can be copied and pasted regardless of visibility state.
* The blinking cursor animation is disabled for people who [prefer reduced motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion)

## Implementation Gotchas

It is possible the non-trivial implementation of `typewritten-text` can lead to unexpected complications with advanced customization.

Most notably, `typewritten-text` works by **cloning** its inner content into a separate custom element called `typewritten-text-mirror`, within which each letter is wrapped with a `span` denoted with the class `typewritten-text_character`. The following is an example before-and-after of what the resulting markup looks like once the element has finished rendering:

```html
<typewritten-text>Hey</typewritten-text>

<!-- ...becomes... -->

<typewritten-text>Hey<typewritten-text-mirror slot="mirror" aria-label="Hey">
<span class="typewritten-text_word">
<span class="typewritten-text_character" aria-hidden="true"></span>
<span class="typewritten-text_character" aria-hidden="true">H</span>
<span class="typewritten-text_character" aria-hidden="true">e</span>
<span class="typewritten-text_character" aria-hidden="true">y</span>
</span>
</typewritten-text-mirror></typewritten-text>
```

The only part that becomes visible to the viewer is the contents of `typewritten-text-mirror`. As a result, a selector like `typewritten-text > span` will have unexpected results.

This architecture has the following explicit goals:

* Preserve, as much as possible, the way the web developer has specified the usage of the element. This means not overriding the inner content of `typewritten-text`.
* Allow the use of semantic markup within `typewritten-text` so it acts as much as possible like a native text-level element
* Enable typing each individual character regardless of its formatting, allowing for size- and position-independence.
Binary file added components/typewritten-text/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions components/typewritten-text/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@auroratide/typewritten-text",
"version": "0.1.4",
"description": "The text types itself out!",
"keywords": [
"text",
"element",
"typing",
"web-component",
"typewriter"
],
"type": "module",
"main": "lib/index.js",
"module": "lib/index.js",
"scripts": {
"clean": "rm -rf lib",
"build": "cp src/*.css lib/ && tsc",
"build:watch": "cp src/*.css lib/ && tsc -w",
"test": "wtr --node-resolve --port 10005 test",
"test:clean": "pnpm clean && pnpm build && pnpm test"
},
"author": {
"name": "Timothy Foster",
"url": "https://auroratide.com"
},
"license": "ISC",
"devDependencies": {
"@open-wc/testing": "^4.0.0",
"@web/test-runner": "^0.18.0",
"typescript": "^5.3.3"
}
}
11 changes: 11 additions & 0 deletions components/typewritten-text/src/define.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { TypewrittenTextElement } from "./index.js"
import { TypewrittenTextMirror } from "./mirror.js"

if (!window.customElements.get(TypewrittenTextMirror.elementName)) {
console.log("DEFINED I GUESS")
window.customElements.define(TypewrittenTextMirror.elementName, TypewrittenTextMirror)
}

if (!window.customElements.get(TypewrittenTextElement.defaultElementName)) {
window.customElements.define(TypewrittenTextElement.defaultElementName, TypewrittenTextElement)
}
21 changes: 21 additions & 0 deletions components/typewritten-text/src/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export const NEXT_CHAR = "typewritten-text:nextchar"
export const nextCharEvent = (position: number) => new CustomEvent(NEXT_CHAR, {
detail: { position },
})

export const PREV_CHAR = "typewritten-text:prevchar"
export const prevCharEvent = (position: number) => new CustomEvent(PREV_CHAR, {
detail: { position },
})

export const PHRASE_TYPED = "typewritten-text:phrasetyped"
export const phraseTypedEvent = () => new CustomEvent(PHRASE_TYPED)

export const PHRASE_REMOVED = "typewritten-text:phraseremoved"
export const phraseRemovedEvent = () => new CustomEvent(PHRASE_REMOVED)

export const STARTED = "typewritten-text:started"
export const startedEvent = () => new CustomEvent(STARTED)

export const PAUSED = "typewritten-text:paused"
export const pausedEvent = () => new CustomEvent(PAUSED)
Loading

0 comments on commit aa9723b

Please sign in to comment.