I am a student studying JavaScript.
I found the js code for the scrambled text animation, but I want to stop looping.
(Because I want to read the contents)
Anyone can explain to stop looping in the 'for or if' part?
Also, are there any unnecessary parts of the JavaScript code?
Thanks in advance for the answer.
html
// ——————————————————————————————————————————————————
// TextScramble
// ——————————————————————————————————————————————————
class TextScramble {
constructor(el) {
this.el = el
this.chars = 'かきくけこらりるれろ'
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 class="dud">${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 = [
'ロレム・イプサムの嘆き、トマト大好き学部のエリット、しかし時と活力、そのような労働と悲しみ、ブラインド行うにはいくつかの重要な事柄に座ります。長年にわたり、私は学区と長寿であれば、そのような刺激の取り組み、彼女のうち、運動の利点を分注を邪魔されたする人が来ます。クピダタットのつるの痛みになりたい宿題に、批判されてきたら痛み、マグナ逃亡しても結果の喜びを生成しません。先例クピダタットブラックは先例していない、つまり、彼らはあなたの悩みに責任がある人の、一般的な義務を捨て、魂を癒しています。'
]
const el = document.querySelector('.text')
const fx = new TextScramble(el)
let counter = 0
const next = () => {
fx.setText(phrases[counter]).then(() => {
setTimeout(next, 800)
})
counter = (counter + 1) % phrases.length
}
next()
<div id="main">
<div class="container">
<div class="glitch" data-text="About">About</div>
<div class="glow">About</div>
</div>
<div class="scanlines"></div>
<div class="text"></div>
</div>
I think you should try adding delay in your loop
see if this helps you
for (let i=0; i<10; i++) {
task(i);
}
function task(i) {
setTimeout(function() {
// Add tasks to do
}, 2000 * i);
}
I'm building a video game where a spaceship moves with controllers and it must avoid the fireball in order to continue to play. If it collides into the fireball the game must display "Game Over" and restart.
At the beginning of the game, there is an input where the user puts his name. Then there is a countdown and then the game starts. I would like that the user gets back to the countdown instead of the point where he must input his name. Does someone knows how to do this?
Code for input:
<form id="askName" title="Write your name">
<label> Enter your username: </label>
<input id="input" type="text" maxlength="10" autofocus>
<button type="button" onclick="countDown(); return Username()" id="begin-timer">
Submit
</button>
let icon = document.getElementById("icon")
let fireballElement = document.querySelector("#fireball")
var input = document.getElementById("input")
function Username(field) {
field = input.value
if (field == "") { alert("Complete blanks"); return false }
document.getElementById("askName").style.display = "none"
setTimeout(function() {
document.getElementById("name").innerHTML = "Player: " + field
icon.style.display = 'block'
fireballElement.style.display = "block"
checkCollision()
}, 4000)
}
CountDown
var count = 3
function countDown() {
function preventCountFast() {
document.getElementById("count").innerHTML = count
if (count > 0) { count-- }
else {
clearInterval(ncount);
document.getElementById("count").style.display = "none"
}
}
var ncount = setInterval(preventCountFast, 1000)
}
Detect collision when fireball touches spaceship:
function checkCollision() {
var elem = document.getElementById("icon")
var elem2 = document.getElementById("fireball")
if (detectOverlap(elem, elem2) && elem2.getAttribute('hit') == 'false') {
hits++
elem2.setAttribute('hit', true)
//THIS IS WHERE YOU SHOULD LOOK AT
document.querySelector("#stopGame").style.display = "inline"
}
setTimeout(checkCollision, 20)
}
var detectOverlap = (function() {
function getPositions(elem) {
var pos = elem.getBoundingClientRect()
return [[pos.left, pos.right], [pos.top, pos.bottom]]
}
function comparePositions(p1, p2) {
var r1, r2
r1 = p1[0] < p2[0] ? p1 : p2
r2 = p1[0] < p2[0] ? p2 : p1
return r1[1] > r2[0] || r1[0] === r2[0]
}
return function(a, b) {
var pos1 = getPositions(a), pos2 = getPositions(b)
return comparePositions(pos1[0], pos2[0]) && comparePositions(pos1[1], pos2[1])
}
})()
<img src="Photo/fireball.png" id="fireball" style="display:none>
<img src="Photo/Spaceship1.png" id="icon" style="display:none">
<h2 id="stopGame"> Game Over! </h2>
Fireball movement:
function fFireball(offset) {
return Math.floor(Math.random() * (window.innerWidth - offset))
}
let fireball = { x: fFireball(fireballElement.offsetWidth), y: 0 }
const fireLoop = function() {
fireball.y += 1
fireballElement.style.top = fireball.y + 'px'
if (fireball.y > window.innerHeight) {
fireball.x = fFireball(fireballElement.offsetWidth)
fireballElement.style.left = fireball.x + 'px'
fireball.y = 0
fireballElement.setAttribute('hit', false )
}
}
fireballElement.style.left = fireball.x + 'px'
let fireInterval = setInterval(fireLoop, 1000 / 200)
Spaceship movement:
//Spaceship moves into space + prevent going out borders
let hits = 0
let display = document.getElementById("body")
let rect = icon
let pos = { top: 1000, left: 570 }
const keys = {}
window.addEventListener("keydown", function(e) { keys[e.keyCode] = true })
window.addEventListener("keyup" , function(e) { keys[e.keyCode] = false })
const loop = function() {
if (keys[37] || keys[81]) { pos.left -= 10 }
if (keys[39] || keys[68]) { pos.left += 10 }
if (keys[38] || keys[90]) { pos.top -= 10 }
if (keys[40] || keys[83]) { pos.top += 10 }
var owidth = display.offsetWidth
var oheight = display.offsetHeight
var iwidth = rect.offsetWidth
var iheight = rect.offsetHeight
if (pos.left < 0) pos.left = -10
if (pos.top < 0) pos.top = -10
if (pos.left + iwidth >= owidth ) pos.left = owidth - iwidth
if (pos.top + iheight >= oheight) pos.top = oheight- iheight
rect.setAttribute("data", owidth + ":" + oheight)
rect.style.left = pos.left + "px"; rect.style.top = pos.top + "px"
}
let sens = setInterval(loop, 1000 / 60)
Convert your countDown() to this:
function countDown(count) {
function preventCountFast() {
document.getElementById("count").innerHTML = count
document.getElementById("count").style.display = "block"
if (count > 0) { count-- }
else { clearInterval(ncount); document.getElementById("count").style.display = "none" }
}
var ncount = setInterval(preventCountFast, 1000)
}
So, while calling this, always call it as countDown(3) it will be easier. Next, modify your checkCollision() just a bit:
function checkCollision() {
var elem = document.getElementById("icon")
var elem2 = document.getElementById("fireball")
if (detectOverlap(elem, elem2) && elem2.getAttribute('hit') == 'false') {
hits++
elem2.setAttribute('hit', true)
document.querySelector("#stopGame").style.display = "block"
document.querySelector("#stopGame").style.animation = "seconds 5s forwards"
/* Added these three lines so that countDown() is called again if collision
occurs*/
setTimeout(function () {
countDown(5);
}, 2000);
/* End of edit */
}
setTimeout(checkCollision, 1)
}
Create a restart function and call it at the end of checkCollision like below.
function checkCollision() {
var elem = document.getElementById("icon")
var elem2 = document.getElementById("fireball")
if (detectOverlap(elem, elem2) && elem2.getAttribute('hit') == 'false') {
hits++
elem2.setAttribute('hit', true)
document.querySelector("#stopGame").style.display = "block"
document.querySelector("#stopGame").style.animation = "seconds 5s forwards"
// call restart at end of game
restart();
return;
}
setTimeout(checkCollision, 1)
}
function restart() {
// clear fireball animation
clearInterval(fireInterval);
count = 3;
let countElement = document.getElementById("count");
let stopGame = document.querySelector("#stopGame");
// show game over for 3 seconds to user
setTimeout(function () {
stopGame.style.animation = "";
stopGame.style.display = icon.style.display = fireballElement.style.display =
"none";
countElement.innerHTML = "";
countElement.style.display = "block";
countElement.style.transform = "scale(1)";
// allow count rerender
setTimeout(function () {
fireballElement.setAttribute("hit", false);
countDown();
// wait for count down to be over
setTimeout(function () {
// reset player position
pos.left = display.offsetWidth / 2;
pos.top = display.offsetHeight;
icon.style.display = "block";
// reset fireball position
fireball = { x: fFireball(display.offsetWidth / 2), y: 0 };
fireballElement.style.top = fireball.y + "px";
fireballElement.style.left = fireball.x + "px";
fireballElement.style.display = "block";
clearInterval(sens);
// restart loop list
sens = setInterval(loop, 1000 / 60);
fireInterval = setInterval(fireLoop, 1000 / 200);
checkCollision();
}, 4000);
}, 1000);
}, 3000);
}
Separate logic from presentation
The reason why you encountered this problem is that your code is tightly coupled to the presentation (DOM), and makes use of the global scope.
Your game logic should be separated from the presentation - DOM is basically an I/O device. Otherwise you tie yourself to a particular implementation (imagine refactoring this to a React application or using Material design, etc).
The principle is called "separation of concerns" (or SoC), and is a well-known principle of software design that will serve you well in the future.
Part 1. Counter
If you rely on a global variable like count to launch the timer, you will inevitably encounter issues with resetting it - make the state internal and only pass in configuration (start, end, and what to do on each step).
function getCounter({
init = 3,
onEnd = () => {},
onStep = () => {},
until = 0
} = {}) {
let curr = init;
return {
interval : null,
start() {
this.interval = setInterval(() => {
onStep(this, curr--);
const shouldStop = until === curr;
shouldStop && this.stop();
}, 1e3);
},
stop() {
const { interval } = this;
clearInterval(interval);
onEnd();
}
};
}
const counter = getCounter({
onStep : (counter, curr) => console.log(curr),
onEnd : () => console.log("ended")
});
counter.start();
Part 2. Game Over
Instead of controlling the UI upon collision, control what should happen, and defer coupling to a particular API:
const detectOverlap = () => !!Math.floor( Math.random() * 8 ); //mock for testing
const checkCollision = ({
obj1,
obj2,
interval = 20,
curHits = 0,
maxHits = 0,
onMiss,
onHit,
onGameOver
}) => {
const isHit = detectOverlap(obj1, obj2);
isHit && curHits++;
const hitOrMissConfig = {
curHits,
maxHits,
obj1,
obj2
};
isHit ? onHit(hitOrMissConfig) : onMiss(hitOrMissConfig);
const timeout = setTimeout(
() => checkCollision({
obj1, obj2, interval,
curHits, maxHits,
onHit, onMiss, onGameOver
}),
interval
);
if (curHits >= maxHits) {
clearTimeout(timeout);
return onGameOver();
}
};
const obj1 = { id : 1 };
const obj2 = { id : 2 };
const onHit = () => console.log("hit!");
const onMiss = () => console.log("miss!");
const onGameOver = () => console.log("game over!");
checkCollision({ obj1, obj2, onHit, onMiss, onGameOver, maxHits : 8 });
Part 3. Start logic
Instead of starting the game from the name form, you should encapsulate your logic and call it as a callback - this way you will have control over when to initiate the name form, countdown or anything else:
const loadForm = ({ parent = document.body, defaultUname, onSubmit } = {}) => {
const form = document.createElement("form");
const input = document.createElement("input");
input.name = "name";
input.type = "text";
input.value = defaultUname;
const start = document.createElement("button");
start.type = "button";
start.innerText = "Start";
form.append(input, start);
parent.append(form);
start.addEventListener("click", (event) => {
onSubmit({ name : input.value, firstTime : false });
form.remove();
});
};
//mocks for testing
const checkCollision = () => console.log("checking collision");
const countDown = (init) => {
if(init) {
console.log(init);
setTimeout(() => countDown(--init), 1e3);
}
};
const startGame = ({ name = "Player 1", firstTime = true } = {}) => {
let restarted = false;
if(!restarted && firstTime) {
return loadForm({
defaultUname : name,
onSubmit : startGame
});
}
countDown(3);
};
startGame();
All Steps combined
You will have to implement the UI handling, guards against no name, and connect collision detection, but this should take care of all the core logic. You might also want to make your fireball and spaceship proper JavaScript objects, and not impose logic on DOM elements for the reasons stated above.
function getCounter({
init = 3,
onEnd = () => {},
onStep = () => {},
until = 0
} = {}) {
let curr = init;
return {
interval : null,
start() {
this.interval = setInterval(() => {
onStep(this, curr--);
const shouldStop = until === curr;
shouldStop && this.stop();
}, 1e3);
},
stop() {
const { interval } = this;
clearInterval(interval);
onEnd();
}
};
}
const detectOverlap = () => !!Math.floor(Math.random() * 4); //mock for testing
const checkCollision = ({
obj1,
obj2,
interval = 20,
curHits = 0,
maxHits = 0,
onMiss,
onHit,
onGameOver
}) => {
const isHit = detectOverlap(obj1, obj2);
isHit && curHits++;
const hitOrMissConfig = {
curHits,
maxHits,
obj1,
obj2
};
isHit ? onHit(hitOrMissConfig) : onMiss(hitOrMissConfig);
const timeout = setTimeout(
() => checkCollision({
obj1, obj2, interval,
curHits, maxHits,
onHit, onMiss, onGameOver
}),
interval
);
if (curHits >= maxHits) {
clearTimeout(timeout);
return onGameOver();
}
};
const loadForm = ({
parent = document.body,
defaultUname,
onSubmit
} = {}) => {
const form = document.createElement("form");
const input = document.createElement("input");
input.name = "name";
input.type = "text";
input.value = defaultUname;
const start = document.createElement("button");
start.type = "button";
start.innerText = "Start";
form.append(input, start);
parent.append(form);
start.addEventListener("click", (event) => {
onSubmit({
name: input.value,
firstTime: false
});
form.remove();
});
};
const startGame = ({
name = "Player 1",
firstTime = true
} = {}) => {
if (firstTime) {
return loadForm({
defaultUname: name,
onSubmit: startGame
});
}
console.log(`Get ready, ${name}`);
const counter = getCounter({
onStep : (_,count) => console.log(count),
onEnd : () => checkCollision({
interval : 1e2,
maxHits : 8,
obj1 : { id : 1 },
obj2 : { id : 2 },
onHit : () => console.log("hit!"),
onMiss : () => console.log("miss!"),
onGameOver : () => {
console.log("game over!");
startGame({ name, firstTime : false });
}
})
});
counter.start();
};
startGame();
Useful Resources
How I separate logic from presentation?
I'm getting a warning in my browser when I am trying to set the state of my context provider. The warning in the browser console is:
Warning: Cannot update a component (`DataContextProvider`) while rendering a different component (`PastLeaveReqUpperLinks`).
How do I go about finding this? I think I need to apply useEffect() hooks to my set methods, but when I try, it says I cannot use a hook inside a callback.
My code is below - I think the issue lies within the modal (commented with MODAL START) - notably the setUnderlayShow() and modalOnOff() set functions that set the state of the context provider.
import React, { useContext, useEffect, useState } from 'react';
import { DataContext } from '../../contexts/DataContext';
import axios from 'axios';
import '../../styles/leaveRequests.css';
import loader from '../../img/unitIcons/loader.svg';
import tick from '../../img/unitIcons/tick.svg';
import cross from '../../img/unitIcons/cross.svg';
import close from '../../img/modals/close.svg';
import '../../styles/modals.css';
const PastLeaveRequestsUnits = () => {
const {
underlayShow,
setUnderlayShow,
modalState,
modalOnOff,
requests,
} = useContext(DataContext);
// - - - - - M O D A L - S T A R T - - - - -
// modalOnOff is a fct in the DataContext which turns modal on/off
// is it also used in LeaveReqsPage.js for the underlay element
// modal: store selected unit for modal to use to determine which data
let [selectedUnit, setSelectedUnit] = useState('');
let updateSelectedUnit = (item) => {
setSelectedUnit(item);
};
// modal - open: fct to update the modalState and update selected unit
const openModal = (item) => {
updateSelectedUnit(item);
modalOnOff();
// uses DataContext's underlay state
setUnderlayShow(!underlayShow);
};
// modal - close: fct to update the modalState and update selected unit
const closeModal = (item) => {
updateSelectedUnit(item);
modalOnOff();
setUnderlayShow(!underlayShow);
};
// - - - - - M O D A L - E N D - - - - -
// COMBINE ALL LEAVE TYPES
// map through all requests and store each request into a new array
let allRequests = [];
if (requests.annualLeave) {
requests.annualLeave.forEach((item) => {
allRequests.push(item);
});
}
if (requests.tilRequest) {
requests.tilRequest.forEach((item) => {
allRequests.push(item);
});
}
if (requests.customReq) {
requests.customReq.forEach((item) => {
allRequests.push(item);
});
}
// sort requests array by startdate
var sortedRequests = allRequests.sort(function (a, b) {
return a.start == b.start ? 0 : +(a.start > b.start) || -1;
});
// Mapper: map through leave units
const getLeaveUnits = sortedRequests.map((item) => {
// determine unit colour
let unitColour = '';
if (item.approved === null) {
unitColour = 'unit reqPending';
} else if (item.approved === true) {
unitColour = 'unit reqApproved';
} else if (item.approved === false) {
unitColour = 'unit reqDeclined';
}
// determine status icon
let statusIcon = loader;
if (item.approved === null) {
statusIcon = loader;
} else if (item.approved === true) {
statusIcon = tick;
} else {
statusIcon = cross;
}
// determine leave type name
let leaveTypeName = '';
if (item.type === 'annualLeave') {
leaveTypeName = 'Annual Leave';
} else if (item.type === 'tilRequest') {
leaveTypeName = 'Time in Lieu';
} else {
leaveTypeName = item.type;
}
// rounder fct for use in convertTimestamp()
const rounder = (num, rounder) => {
var multiplier = 1 / rounder;
return Math.round(num * multiplier) / multiplier;
};
// convert timestamp
const convertTimestamp = (timestamp, type) => {
let days = timestamp / 1000 / 60 / 60 / 24;
// for A/L leave types
if (type === 'annualLeave') {
let value = rounder(days, 0.5);
let d = 'days';
if (value === 1) {
d = 'day';
}
if (value < 0.5) {
return `0.5 days`;
} else {
return `${value} ${d}`;
}
// for til leave types
} else if (type === 'tilRequest') {
let mins = Math.floor(timestamp / 1000 / 60);
if (mins > 59) {
let hours = Math.floor(mins / 60);
let modul = mins % (hours * 60);
if (modul > 0) {
return `${hours}h ${modul}m`;
} else {
return `${hours}h`;
}
} else {
return `${mins} mins`;
}
} else {
let days = timestamp / 1000 / 60 / 60 / 24;
let value = rounder(days, 0.5);
let d = 'days';
if (value === 1) {
d = 'day';
}
if (value < 0.5) {
return `0.5 days`;
} else {
return `${value} ${d}`;
}
}
};
// start & end date formatter
const startEndDate = (start, end) => {
// removed irrelevant code
return `${startDay} ${startMonth} ${startYr} ${divider} ${endDay} ${endMonth} ${endYr}`;
};
// if unit is older than today, show
// today as a timestamp
let today = Math.round(new Date().getTime());
// if modal open, scroll-lock the requests container
// if (modalState) {
// $('.innerUnitsContainer').css('overflow', 'hidden');
// } else {
// $('.innerUnitsContainer').css('overflow', 'auto');
// }
// modal: template
const modal = () => {
let status = '';
// need to make a function to return name of manager
let manager = 'Sean Wheldon';
let submitDate = () => {
let dateObj = new Date(selectedUnit.submitDate);
let day = dateObj.toLocaleString('en-US', { day: 'numeric' });
let month = dateObj.toLocaleString('en-US', { month: 'short' });
return `${day} ${month}`;
};
if (selectedUnit.approved === true) {
status = `Approved by ${manager}`;
} else if (selectedUnit.approved === null) {
status = `Submitted on ${submitDate()}`;
} else if (selectedUnit.approved === false) {
status = `Declined by ${manager}`;
}
let editButton = () => {
if (selectedUnit.approved !== false) {
return <p className='editButton'>EDIT REQUEST</p>;
}
};
return (
<div>
<img
src={close}
className='closeBtn'
onClick={closeModal}
alt='Close'
/>
<p className='modalTitle'>
{startEndDate(selectedUnit.start, selectedUnit.end)}
</p>
<p className='requestStatus'>{status}</p>
<div onClick={closeModal}>{editButton()}</div>
</div>
);
};
if (item.end <= today && item.approved !== false) {
return (
<div
className={unitColour}
key={item.reqID}
onClick={() => {
openModal(item);
}}
>
<div className='unitLeft'>
<img src={statusIcon} alt='Status Icon' id='statusIcon' />
</div>
<div className='unitMiddle'>
<p id='unitLeaveType'>{leaveTypeName}</p>
<p id='unitDate'>{startEndDate(item.start, item.end)}</p>
</div>
<div className='unitDivider'></div>
<div className='unitRight'>
<p id='unitDuration'>
{convertTimestamp(item.duration, item.type)}
</p>
</div>
{/* M O D A L */}
<div
className={`modalBackground ${!!modalState ? 'open' : ''}`}
onClick={(e) => {
e.stopPropagation();
}}
>
{modal()}
</div>
{/* E N D M O D A L */}
</div>
);
}
});
// render
return <div className='requestsContainer'>{getLeaveUnits}</div>;
};
export default PastLeaveRequestsUnits;
Hi I was trying to implement this text scrable
https://codepen.io/soulwire/pen/mErPAK/?editors=1010
in my react app, but I'm receiving an error TypeError: Cannot read property 'innerText' of null.
9 | this.update = this.update.bind(this)
10 | }
11 | setText(newText) {
> 12 | const oldText = this.el.innerText
13 | const length = Math.max(oldText.length, newText.length)
14 | const promise = new Promise(resolve => (this.resolve = resolve))
15 | this.queue = []
so far this is what I did
https://codesandbox.io/s/oxm38v7x9y
Created new component scrable.js
Moved the code from codepen
Imported to index.js
you don't need to fix the codesandbox, just a little clue is enough :)
import React, { Component } from "react"
export default class Scrable extends Component {
render() {
const phrases = [
"Neo,",
"sooner or later",
"you're going to realize",
"just as I did",
"that there's a difference",
"between knowing the path",
"and walking the path",
]
const el = document.querySelector(".text")
const fx = new TextScramble(el)
console.log(el)
let counter = 0
const next = () => {
fx.setText(phrases[counter]).then(() => {
setTimeout(next, 800)
})
counter = (counter + 1) % phrases.length
}
next()
return (
<TextScramble>
<div className="text" />
</TextScramble>
)
}
}
export class TextScramble extends Component {
constructor(el) {
super()
this.el = el
this.chars = "!<>-_\\/[]{}—=+*^?#________"
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 class="dud">${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)]
}
render() {
return <div />
}
}
Hi all thank you for the comments,
I was able to make it work. here's my code below. any suggestions is welcome
I'm not entirely sure it's the right way but it works
import React, { Component } from 'react'
export default class Scrable extends Component {
constructor(el) {
super(el)
this.el = el
this.chars = "!<>-_\\/[]{}—=+*^?#________"
// this.update = this.update.bind(this)
}
componentDidMount(){
const phrases = [
'Neo,',
'sooner or later',
'you\'re going to realize',
'just as I did',
'that there\'s a difference',
'between knowing the path',
'and walking the path'
]
const el = document.querySelector('.text')
const fx = new TextScramble(el)
let counter = 0
const next = () => {
fx.setText(phrases[counter]).then(() => {
setTimeout(next, 800)
})
counter = (counter + 1) % phrases.length
}
next()
console.log(el)
}
render() {
const phrases = [
"Neo,",
"sooner or later",
"you're going to realize",
"just as I did",
"that there's a difference",
"between knowing the path",
"and walking the path",
]
return (
<div>
<div className="text">text</div>
</div>
)
}
}
class TextScramble {
constructor(el) {
this.el = el
this.chars = '!<>-_\\/[]{}—=+*^?#________'
this.update = this.update.bind(this)
console.log(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 class="dud">${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)]
}
}