<main id="main">
  <div id="text" class="js-split-block">
    <span class="js-split-text">   
       Test Text
    </span>
    
    <span class="js-split-text">   
       Test Text02
    </span>
    
    <span class="js-split-text">   
       Test Text03
    </span>
  </div>
</main>
#main {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100vw;
  height: 100vh;
}

#text {
  display: flex;
  flex-direction: column;
  font-size: 5vw;
  color: #333;
  
  & .char {
   display: inline-block;
   min-width: 0.25em;
   animation: translateX 0.4s cubic-bezier(0.39, 0.575, 0.565, 1) both, fadeIn 0.3s cubic-bezier(0.55, 0.055, 0.675, 0.19)  both;
		}
}

.visuallyHidden {
	position: absolute !important;
	width: 1px !important;
	height: 1px !important;
	margin: -1px !important;
	padding: 0 !important;
	overflow: hidden !important;
	clip: rect(0 0 0 0) !important;
	white-space: nowrap !important;
	border: 0 !important;
}

@keyframes translateX {
	0% {
		transform: translateX(25%);
	}
	100% {
		transform: translateX(0);
	}
}

@keyframes fadeIn {
	0% {
		opacity: 0;
	}
	100% {
		opacity: 1;
	}
}

View Compiled
// @ts-check

export class SplitText {
	/**
	 * @class SplitText
	 * @constructor
	 * @param {String} els - 対象要素(メイン)
	 * @param {Object} [Options] - オプション
	 * @param {any} [Options.target] - 対象要素(複数行の場合は行ごとに指定)
	 * @param {Array} [Options.transition] - トランジションディレイがある場合は指定 (ms)
	 * @param {Array} [Options.animation] - アニメーションディレイがある場合は指定 (ms)
	 *
	 * @example
		new SplitText(".js-s-block", {
			target: ".js-s-text",
			transition: [800],
		});
	 */

	// transition, animationはどちらか一方だけ指定する
	constructor(els, { target = false, transition, animation } = {}) {
		this.els = document.querySelectorAll(els);

		if (!this.els) {
			console.error(`SplitText: ${els} が見つかりませんでした。`);
			return;
		}

		this.transition = transition;
		this.animation = animation;

		// 対象要素が複数行の場合は、それぞれに対応する
		// テキストを分割する
		this.els.forEach((element) => {
			this.target = target ? element.querySelectorAll(target) : this.els;

			this.target.forEach((target) => {
				const span = document.createElement("span");
				span.innerHTML = target.innerHTML;
				span.classList.add("visuallyHidden");
				target.insertAdjacentElement("afterend", span);

				this.chars = target.innerHTML.trim().split("");
				target.innerHTML = this._splitText();
			});
		});

		// ディレイを付与する
		if (this.transition || this.animation) {
			this._charsAddDelay();
		}
	}

	_splitText() {
		return this.chars.reduce((acc, curr) => {
			if (curr === "&") {
				curr = curr.replace("&", "&amp;");
			} else if (curr === /\s+/) {
				curr = curr.replace(/\s+/, "&nbsp;");
			}

			return `${acc}<span class="char" aria-hidden="true">${curr}</span>`;
		}, "");
	}

	_charsAddDelay() {
		this.els.forEach((element) => {
			const chars = element.querySelectorAll(".char");
			const transition = this.transition;
			const animation = this.animation;

			chars.forEach((char, index) => {
				let transitionDelayTime;
				let animationDelayTime;

				if (transition) {
					if (transition.length === 1) {
						transitionDelayTime = Math.round(transition[0] * index) / 1000 + "s";
					} else {
						transitionDelayTime = (() => {
							return transition.reduce((acc, curr) => {
								const diff = curr - acc;
								return `${Math.round(acc * index) / 1000}s, ${Math.round(acc * index + diff) / 1000}s`;
							});
						})();
					}
				}
				if (animation) {
					if (animation.length === 1) {
						animationDelayTime = Math.round(animation[0] * index) / 1000 + "s";
					} else {
						animationDelayTime = (() => {
							return animation.reduce((acc, curr) => {
								const diff = curr - acc;
								return `${Math.round(acc * index) / 1000}s, ${Math.round(acc * index + diff) / 1000}s`;
							});
						})();
					}
				}

				if (this.animation && this.transition) {
					char.setAttribute("style", `animation-delay: ${animationDelayTime}; transition-delay: ${transitionDelayTime};`);
				} else if (this.animation) {
					char.setAttribute("style", `animation-delay: ${animationDelayTime};`);
				} else if (this.transition) {
					char.setAttribute("style", `transition-delay: ${transitionDelayTime};`);
				}
			});
		});
	}
}

new SplitText(".js-split-block", {
  target: ".js-split-text",
  animation: [32, 42],
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.