How do I perform an animated transformation on text while performing a fade in effect during the animated transformation?
// ——————————————————————————————————————————————————
// TextScramble
// ——————————————————————————————————————————————————
class TextScramble {
constructor(elm, numWords) {
this.el = el
this.numWords = numWords;
this.chars = '!<>-_\\/[]{}—= *^?#1234567890________'
this.update = this.update.bind(this)
}
setText(newText) {
const oldText = this.el.innerText
const length = Math.max(oldText.length, newText.length)
const promise = new Promise((resolve) => this.resolve = resolve)
this.queue = []
for (let i = 0; i < length; i ) {
const from = oldText[i] || ''
const to = newText[i] || ''
const start = Math.floor(Math.random() * 40)
const end = start Math.floor(Math.random() * 40)
this.queue.push({ from, to, start, end })
}
cancelAnimationFrame(this.frameRequest)
this.frame = 0
this.update()
return promise
}
update() {
let output = ''
let complete = 0
for (let i = 0, n = this.queue.length; i < n; i ) {
let { from, to, start, end, char } = this.queue[i]
if (this.frame >= end) {
complete
output = to
} else if (this.frame >= start) {
if (!char || Math.random() < 0.28) {
char = this.randomChar()
this.queue[i].char = char
}
output = `<span >${char}</span>`
} else {
output = from
}
}
this.el.innerHTML = output
if (complete === this.queue.length) {
this.resolve()
}
else {
this.frameRequest = requestAnimationFrame(this.update)
this.frame
}
}
randomChar() {
return this.chars[Math.floor(Math.random() * this.chars.length)]
}
}
// ——————————————————————————————————————————————————
// Example
// ——————————————————————————————————————————————————
const phrases = {
'Coding' : 'none',
'With' : 'none',
'Muhammad': 'none',
'Coding With Muhammad' : 'fade'
}
let phraseValues = Object.keys(phrases);
const el = document.querySelector('.text')
const fx = new TextScramble(el, phraseValues.length)
let counter = 0
let animation = phraseValues[0];
let animate = () => {
return function(callback) {
document.querySelector(".text").animate([
// keyframes
{ opacity: '0' },
{ opacity: '1' }
], {
// timing options
duration: 3500,
iterations: 1
});
callback();
}
}
const next = () => {
fx.setText(phraseValues[counter]).then(() => {
if (counter <= phraseValues.length)
setTimeout(next, 800)
else {
animation = phrases[phraseValues[counter]]
setTimeout(animate(next), 800)
}
})
counter = (counter 1) % phraseValues.length
}
next()
html, body {
font-family: 'Roboto Mono', monospace;
background: #212121;
height: 100%;
}
.container {
height: 100%;
width: 100%;
justify-content: center;
align-items: center;
display: flex;
}
.text {
font-weight: 100;
font-size: 28px;
color: #FAFAFA;
}
.dud {
color: #757575;
}
.fadeIn {
animation: fade 10s;
}
<div class="container">
<div class="text"></div>
</div>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
CodePudding user response:
I was very close:
I moved the callback invocation statement above the closure return statement below it. I left the animation keyframes as is.
Finally I modified the setTimeout in the phrase to be animated, to be as follows:
I placed a next and animate as callbacks in the setTimeout together to be called back in cascade sequence.
I used a anonymous function definition using arrow notation.
On animate (the function I wrote) I passed in the fx.update function definition as the callback, and invoked the closure function returned animate as the Higher Order Function, like so.
See Results!
// ——————————————————————————————————————————————————
// TextScramble
// ——————————————————————————————————————————————————
class TextScramble {
constructor(elm, numWords) {
this.el = el
this.numWords = numWords;
this.chars = '!<>-_\\/[]{}—= *^?#1234567890________'
this.update = this.update.bind(this)
}
setText(newText) {
const oldText = this.el.innerText
const length = Math.max(oldText.length, newText.length)
const promise = new Promise((resolve) => this.resolve = resolve)
this.queue = []
for (let i = 0; i < length; i ) {
const from = oldText[i] || ''
const to = newText[i] || ''
const start = Math.floor(Math.random() * 40)
const end = start Math.floor(Math.random() * 40)
this.queue.push({ from, to, start, end })
}
cancelAnimationFrame(this.frameRequest)
this.frame = 0
this.update()
return promise
}
update = () => {
let output = ''
let complete = 0
for (let i = 0, n = this.queue.length; i < n; i ) {
let { from, to, start, end, char } = this.queue[i]
if (this.frame >= end) {
complete
output = to
} else if (this.frame >= start) {
if (!char || Math.random() < 0.28) {
char = this.randomChar()
this.queue[i].char = char
}
output = `<span >${char}</span>`
} else {
output = from
}
}
this.el.innerHTML = output
if (complete === this.queue.length) {
this.resolve()
}
else {
this.frameRequest = requestAnimationFrame(this.update)
this.frame
}
}
randomChar() {
return this.chars[Math.floor(Math.random() * this.chars.length)]
}
}
// ——————————————————————————————————————————————————
// Example
// ——————————————————————————————————————————————————
const phrases = {
'Coding' : 'none',
'With' : 'none',
'Muhammad': 'none',
'Coding With Muhammad' : 'fade'
}
let phraseValues = Object.keys(phrases);
const el = document.querySelector('.text')
const fx = new TextScramble(el, phraseValues.length)
let counter = 0
let animation = phraseValues[0];
let animate = (callback) => {
callback();
return function() {
document.querySelector(".text").animate([
// keyframes
{ opacity: '0' },
{ opacity: '1' }
], {
// timing options
duration: 3500
});
}
}
const next = () => {
fx.setText(phraseValues[counter]).then(() => {
if (counter < phraseValues.length-1)
setTimeout(next, 800)
else {
setTimeout(() => {next, animate(next, fx.update)()}, 800)
}
})
counter = (counter 1) % phraseValues.length
}
next()
html, body {
font-family: 'Roboto Mono', monospace;
background: #212121;
height: 100%;
}
.container {
height: 100%;
width: 100%;
justify-content: center;
align-items: center;
display: flex;
}
.text {
font-weight: 100;
font-size: 28px;
color: #FAFAFA;
}
.dud {
color: #757575;
}
<div class="container">
<div class="text"></div>
</div>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>