character loops

by Laurens Maneschijn

HTML

<hr>
Test character loops for e.g. text only loading animation.
<hr>

<details><summary>Edit characters: ...</summary>
One loop on each line:
<textarea id="source">
1234
❶❷❸❹
➀➁➂➃
⓵⓶⓷⓸

Hello world.   
bdqp
abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ

⠓⠋⠙⠚
⠦⠖⠲⠴
⠶⠧⠏⠛⠹⠼
⣶⣧⣏⡟⠿⢻⣹⣼
⠉⠛⠿⣿⣶⣤⣀ 

|/-\|/-\
˄˃˅˂
▲▶▼◀
⮫⮯⮨⮬
⏫⏩⏬⏪

↑↗→↘↓↙←↖
↗↘↙↖
⇑⇗⇒⇘⇓⇙⇐⇖
⇗⇘⇙⇖
⬆⬈➡⬊⬇⬋⬅⬉
⬈⬊⬋⬉

▝▗▖▘
▙▛▜▟
◳◲◱◰
◷◶◵◴
◓◑◒◐
◝◞◟◜

⏳⌛
⧗⧖
⊕⊗
⨁⨂

🞊🞉🞈🞇🞆🞅
🞉🞈🞇🞆🞅
🞉🞈🞇🞆🞅🞆🞇🞈
🞉🞈🞇🞆🞅🞅🞆🞇🞈🞉
🞅🞆🞇🞈🞉◉⚬🞄·

🂡🂢🂣🂤🂥🂦🂧🂨🂩🂪🂫🂬🂭🂮
🂱🂲🂳🂴🂵🂶🂷🂸🂹🂺🂻🂼🂽🂾
🃁🃂🃃🃄🃅🃆🃇🃈🃉🃊🃋🃌🃍🃎
🃑🃒🃓🃔🃕🃖🃗🃘🃙🃚🃛🃜🃝🃞
🕛🕐🕑🕒🕓🕔🕕🕖🕗🕘🕙🕚
🌑🌒🌓🌔🌕🌖🌗🌘

⌜⌝⌟⌞
{"parts":["⌜ "," ⌝"," ⌟","⌞ "],"style":{"font-family":"monospace","white-space":"pre"}}
{"parts":["⌜ \n  "," ⌝\n  ","  \n ⌟","  \n⌞ "],"style":{"font-family":"monospace","white-space":"pre","line-height":"1"}}
{"parts":[" ⬆ \n   \n   ","  ⬈\n   \n   ","   \n  ➡\n   ","   \n   \n  ⬊","   \n   \n ⬇ ","   \n   \n⬋  ","   \n⬅  \n   ","⬉  \n   \n   "],"style":{"font-family":"monospace","white-space":"pre","line-height":"1"}}

</textarea>
</details>

<hr>

<button class="square" onclick="stop();"    > ⏹ </button>
<button class="square" onclick="toggle();"  > ⏯ </button>
<button class="square" onclick="tick();"    > 1 </button>
<button class="square" onclick="restart();" > ▶ </button>

<input type="range"
	min="0"
	max="1000"
	step="10"
	value="100"
	ondblclick="this.value=100; this.onchange();"
	onchange="delay=this.value;this.title=this.value;"
	onmouseover="this.title=this.value";
>

<input type="checkbox" onchange="reverse = this.checked;" >

<br>

font: <select id="fontfamily"></select>

<input type="range"
	min="0"
	max="40"
	step="1"
	ondblclick="this.value=20; this.onchange();"
	onchange="document.querySelector('#target').style.fontSize=this.value+'px';this.title=this.value;"
	onmouseover="this.title=this.value";
>

<hr>

<div id="target"></div>

<hr>

amp-what examples: 
<a target="_blank" href="http://www.amp-what.com/unicode/search/braille%20pattern%20dots">⣿</a>
<a target="_blank"...

CSS

* {
	box-sizing: border-box;
}

#target {
	font-size: 20px;
	font-family: monospace;
}

span.charset {
	display: inline-block;
	margin: 2px;
	padding: 2px;
	min-width: 1.7em;
	min-height: 1.7em;
	background: #8888;
	border-radius: 0;
	text-align: center;
	vertical-align: middle;
	line-height: 1.5;
}

textarea {
	white-space: pre;
	width: 100%;
	height: 200px;
	resize: vertical;
}

details > summary {
	cursor: pointer;
	user-select: none;
	border: 2px outset #888;
	background: #8882;
}

button.square {
	vertical-align: middle;
	line-height: 1.5;
	aspect-ratio: 1/1;
}

input[type="range"] {
	position: relative;
}
input[type="range"]::after {
	content: attr(title);
	position: absolute;
	z-index: 9;
	top: 15px;
}

JavaScript

var _target = document.querySelector('#target');
var _source = document.querySelector('#source');
var charsets = [];
var running = false;
var delay = 100;
var reverse = false;

init();
function init() {
	console.clear();
	_source.addEventListener('change', restart);
	_source.addEventListener('keyup', restart);
	restart();
	init_fontfamily_select();
}

function readCharSets() {
	_target.innerHTML = '';
	charsets = [];
	lines = _source.value || '';
	lines.split('\n')
//	.filter(s => s.trim() !== '')
	.forEach((line) => {
		if (line.trim() === '') {
			// add a newline in display target:
			target.appendChild(document.createElement('div'));
			return;
		}

		var charset = {str: line};

		// support complex lines as a json line
		// e.g.: '{"parts":["1","2","3","4"],"style":{}}';
		if (line[0] === '{') {
			try {
				var parsed = JSON.parse(line);
				console.log(parsed, line);
				charset.str = parsed.str;
				charset.parts = parsed.parts;
				charset.style = parsed.style;
			} catch (e) {
				console.log(e);
			}
		}

//		if (containsSurrogatePair(line)) {
//			console.log('containsSurrogatePair:', line);
//		}
		if (charset.str && !charset.parts) {
			charset.parts = characterArrayFromString(charset.str);
		}
//		if (!charset.str && charset.parts) {
//			charset.str = charset.parts.join('');
//		}

		if (!charset.element) {
			var el = charset.element = document.createElement('span');
			el.className = 'charset';
			if (charset.style) {
				Object.assign(el.style, charset.style);
			}
			target.appendChild(el);
		}

		charsets.push(charset);
	});
}

function charsetNextLoopStep(charset) {
	charset.i = charset.i || 0;

	charset.element.textContent = charset.parts[charset.i];

	charset.i += reverse ? -1 : 1;
	charset.i %= charset.parts.length;
	if (charset.i < 0) {
		charset.i += charset.parts.length;
	}
}

var af;
var af_to;
function tick(timestamp) {
	charsets.forEach((charset) => {
		charsetNextLoopStep(charset);
	});
	if (running)...