I'm coding a search algorithms visualizer in react and I'm having a small glitch whenever I move my start or end nodes. When I press down on the mouse and move the cursor over the board, the nodes update accordingly. That is until the cursor goes beyond the boundaries of the grid, leaving a node with the start-node or end-node classname, visually creating two (or more) nodes, as such:
This doesn't affect functionality but it is pretty annoying. Ideally, I'm looking to make it work like it does on this demo: https://pathfindout.com/, where it doesn't matter where the user moves the cursor while dragging the nodes, they always stay inside the limits of the board. This is the code for my Node component so far:
const Node = ({ row, col, isWall, isStart, isEnd, handleState, handleMouseState }) => {
const { nodesMatrix, updateNodes } = handleState;
const { isMouseDown, setIsMouseDown } = handleMouseState;
//This variable helps to prevent the user
//from stacking the 'start' and 'end' nodes
//on top of each other. Instead they jump to
//the next available sibling.
let prevCollision = null;
const handleMouseDown = (e) => {
e.preventDefault();
isStart ? setIsMouseDown(1) : isEnd ? setIsMouseDown(2) : setIsMouseDown(3);
};
const handleMouseUp = (e) => {
setIsMouseDown(0);
const newParent = e.target.id ? e.target : null;
const [newParentRow, newParentCol] = newParent.id.match(/\d+/g);
if (isMouseDown === 1) updateNodes(false, true, parseInt(newParentRow), parseInt(newParentCol));
if (isMouseDown === 2) updateNodes(false, false, parseInt(newParentRow), parseInt(newParentCol));
if (isMouseDown === 3) updateNodes(true, false, parseInt(newParentRow), parseInt(newParentCol));
};
const handleMouseEnter = (e) => {
const newParent = e.target.id ? e.target : null;
const prevParent = e.relatedTarget;
const [newParentRow, newParentCol] = e.target.id.match(/\d+/g);
const nodePointer = nodesMatrix[newParentRow][newParentCol];
//moving start node
if (isMouseDown === 1) {
const collision = isEnd;
if (!collision) {
prevParent.classList.remove('start-node');
newParent.classList.add('start-node');
nodePointer.isStart = true;
} else {
prevCollision = prevParent;
}
};
//moving end node
if (isMouseDown === 2) {
const collision = isStart;
if (!collision) {
prevParent.classList.remove('end-node');
newParent.classList.add('end-node');
nodePointer.isEnd = true;
} else {
prevCollision = prevParent;
}
}
//creating walls
if (isMouseDown === 3) {
if(!isStart && !isEnd) {
if (isWall) {
newParent.classList.remove('wall-node');
nodePointer.isWall = false;
} else {
newParent.classList.add('wall-node');
nodePointer.isWall = true;
};
}
};
};
const handleMouseLeave = (e) => {
const prevParent = e.target.id ? e.target : null;
const [prevRow, prevCol] = prevParent.id.match(/\d+/g);
const nodePointer = nodesMatrix[prevRow][prevCol];
if (isMouseDown === 1) {
if (prevCollision) prevCollision.classList.remove('start-node');
nodePointer.isStart = false;
};
if (isMouseDown === 2) {
if (prevCollision) prevCollision.classList.remove('end-node');
nodePointer.isEnd = false;
};
};
const handleClick = (e) => {
const target = e.target.id ? e.target : null;
const [row, col] = target.id.match(/\d+/g);
const nodePointer = nodesMatrix[row][col];
if (isWall) {
target.classList.remove('wall-node');
nodePointer.isWall = false;
} else {
target.classList.add('wall-node');
nodePointer.isWall = true;
}
};
return (
<div id={`node-${ row }-${ col }`}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onMouseUp={handleMouseUp}
onMouseDown={handleMouseDown}
onClick={(isStart || isEnd) ? null : handleClick}
className={
`${ isStart ? 'start-node' : isEnd ? 'end-node' : isWall ? 'wall-node' : '' }
node w-6 h-6 border border-blue-400`} />
)
};
export default Node
Any help is greatly appreciated!
Edit:
This is the parent Grid component if it helps with anything:
const Grid = () => {
const [nodesMatrix, setNodesMatrix] = useState([]);
const [speed, setSpeed] = useState(5);
const [startNodeRow, setStartNodeRow] = useState(6);
const [startNodeCol, setStartNodeCol] = useState(10);
const [endNodeRow, setEndNodeRow] = useState(6);
const [endNodeCol, setEndNodeCol] = useState(17);
// 1 to move 'start' node;
// 2 to move 'end' node;
// 3 to create a wall;
const [isMouseDown, setIsMouseDown] = useState(0);
//Initializes Grid
useEffect(() => {
const cells = [];
const startNodeRow = 6;
const startNodeCol = 10;
const endNodeRow = 6;
const endNodeCol = 17;
for (let row = 0; row < ROWS; row++) {
const currentRow = [];
for (let col = 0; col < COLUMNS; col++) {
const node = {
row,
col,
isWall: false,
isStart: row === startNodeRow && col === startNodeCol,
isEnd: row === endNodeRow && col === endNodeCol,
distance: Infinity,
visited: false,
previousNode: null
};
currentRow.push(node);
};
cells.push(currentRow);
};
setNodesMatrix(cells);
}, []);
useEffect(() => {
clearAllNodesStyles();
}, [isMouseDown]);
const resetMatrix = (matrix) => {
const matrixRef = [...matrix]
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLUMNS; col++) {
const node = matrixRef[row][col];
node.distance = Infinity;
node.previousNode = null;
node.visited = false;
node.isWall = node.isStart || node.isEnd ? false : node.isWall
};
};
return matrixRef
};
const updateNodes = (isWall, isStart, newRow, newCol) => {
const newGrid = resetMatrix(nodesMatrix);
if(isWall) return setNodesMatrix(newGrid);
if (isStart) {
setStartNodeRow(newRow);
setStartNodeCol(newCol);
} else {
setEndNodeRow(newRow);
setEndNodeCol(newCol);
};
setNodesMatrix(newGrid);
};
return (
<div className="flex flex-col self-center">
{nodesMatrix.map((row, rowIndex) => {
return (
<div key={rowIndex} className='flex'>
{row.map((node, index) => {
const { row, col, isWall, isStart, isEnd } = node;
return <Node
key={index}
row={row}
col={col}
isWall={isWall}
isStart={isStart}
isEnd={isEnd}
handleState={{nodesMatrix, updateNodes}}
handleMouseState={{isMouseDown, setIsMouseDown}} />
})}
</div>
)
})
}
<button
onClick={() => visualizeDijkstra(nodesMatrix, { startNodeRow, startNodeCol, endNodeRow, endNodeCol, speed })}
className="...">
Search Path
</button>
</div>
)
};
export default Grid
As I mentioned in comment, The problem is, you are not tracking whether the mouse going out of the Grid or not. You are also not storing the last Node before going out of grid. As a result, when the mouse again enters the Grid, the program doesn't know which one is the last node which it visited. So, it can't remove the class from the last node.
import React, { useState, useEffect } from "react";
import { clearAllNodesStyles, visualizeDijkstra } from "../algorithms/dijkstra";
import Node from "./Node";
const COLUMNS = 60;
const ROWS = 25;
const Grid = () => {
const [nodesMatrix, setNodesMatrix] = useState([]);
const [speed, setSpeed] = useState(5);
const [startNodeRow, setStartNodeRow] = useState(6);
const [startNodeCol, setStartNodeCol] = useState(10);
const [endNodeRow, setEndNodeRow] = useState(6);
const [endNodeCol, setEndNodeCol] = useState(17);
// 1 to move 'start' node;
// 2 to move 'end' node;
// 3 to create a wall;
const [isMouseDown, setIsMouseDown] = useState(0);
//Initializes Grid
useEffect(() => {
const cells = [];
const startNodeRow = 6;
const startNodeCol = 10;
const endNodeRow = 6;
const endNodeCol = 17;
for (let row = 0; row < ROWS; row++) {
const currentRow = [];
for (let col = 0; col < COLUMNS; col++) {
const node = {
row,
col,
isWall: false,
isStart: row === startNodeRow && col === startNodeCol,
isEnd: row === endNodeRow && col === endNodeCol,
distance: Infinity,
visited: false,
previousNode: null,
};
currentRow.push(node);
}
cells.push(currentRow);
}
setNodesMatrix(cells);
}, []);
useEffect(() => {
clearAllNodesStyles();
}, [isMouseDown]);
const resetMatrix = (matrix) => {
const copyCat = [...matrix];
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLUMNS; col++) {
const node = copyCat[row][col];
const newNode = {
...node,
distance: Infinity,
previousNode: null,
visited: false,
isWall: node.isStart || node.isEnd ? false : node.isWall,
};
copyCat[row][col] = newNode;
}
}
return copyCat;
};
const updateNodes = (isWall, isStart, newRow, newCol) => {
const newGrid = resetMatrix(nodesMatrix);
if (isWall) return setNodesMatrix(newGrid);
if (isStart) {
setStartNodeRow(newRow);
setStartNodeCol(newCol);
} else {
setEndNodeRow(newRow);
setEndNodeCol(newCol);
}
setNodesMatrix(newGrid);
};
const handleTouchBoundaryLine = (e) => {
const prevParent = e.target.id ? e.target : null;
if (isMouseDown === 1 || isMouseDown === 2) {
if (prevParent) {
const [prevRow, prevCol] = prevParent.id.match(/\d+/g);
const nodePointer = nodesMatrix[prevRow][prevCol];
if (isMouseDown === 1) nodePointer.isStart = true;
if (isMouseDown === 2) nodePointer.isEnd = true;
let clickEvent = document.createEvent("MouseEvents");
clickEvent.initEvent("mouseup", true, true);
prevParent.dispatchEvent(clickEvent);
}
}
};
return (
<div className="flex flex-col self-center">
<div id="grid-boundary" onMouseLeave={handleTouchBoundaryLine}>
{nodesMatrix.map((row, rowIndex) => {
return (
<div key={rowIndex} className="flex">
{row.map((node, index) => {
const { row, col, isWall, isStart, isEnd } = node;
return (
<Node
key={index}
row={row}
col={col}
isWall={isWall}
isStart={isStart}
isEnd={isEnd}
handleState={{ nodesMatrix, updateNodes }}
handleMouseState={{ isMouseDown, setIsMouseDown }}
/>
);
})}
</div>
);
})}
</div>
<button
onClick={() =>
visualizeDijkstra(nodesMatrix, {
startNodeRow,
startNodeCol,
endNodeRow,
endNodeCol,
speed,
})
}
className="h-20 w-full text-white hover:bg-blue-800 bg-blue-700 place-self-end"
>
Search Path
</button>
</div>
);
};
export default Grid;
I have added a boundary line/div around the grid. So, whenever the mouse leaves that div, it will dispatch mouseup event on the previous visited Node and mark it as the start/end Node.
Related
i have one datatable. Datatable has result and pagination. Result works fine, but when filtering, pagination moves according to result. This is normal page.
Filtering but still 9 page. And result 10.
now result 20 but page 5. Because in normal case, when you make 20, it becomes 5 pages. How can i fix?
const [currentPage, setCurrentPage] = useState(1);
const [select, setSelect] = useState(10);
function handleChange(e) {
setSelect(e.target.value);
}
const indexOfLast = currentPage * select;
const indexOfFirst = indexOfLast - select;
const splitData = rows.slice(indexOfFirst, indexOfLast);
const totalPages = Math.ceil(rows.length / select);
const renderHeadingRow = (item) => {
return <td>{item}</td>;
};
const renderRow = (item, index) => {
console.log(splitData[index])
return (
<tr key={index}>
{splitData[index]
?.filter((val) => {
if (searchWord == "") {
return val;
}
else if(item[1].toLowerCase().includes(searchWord.toLowerCase())){
return val
}
else if(item[2].toLowerCase().includes(searchWord.toLowerCase())){
return val
}
else if(item[5].toLowerCase().includes(searchWord.toLowerCase())){
return val
}
})
.map((data, no) => {
return <td>{splitData[index][no]}</td>;
})}
</tr>
);
};
const theadData = <tr>{heading.map(renderHeadingRow)}</tr>;
const tbodyData = splitData.map(renderRow);
You can try to use useEffect to have a side-effect for row filtering. All your displayed data and total page calculation need to rely on filtered rows instead of original rows.
const [currentPage, setCurrentPage] = useState(1);
const [select, setSelect] = useState(10);
const [filteredRows, setFilteredRows] = useState(rows || [])
function handleChange(e) {
setSelect(e.target.value);
}
React.useEffect(() => {
const updatedCurrentPage = 1
const updatedRows = rows.filter((item) => {
if (searchWord == "") {
return item;
}
else if(item[1].toLowerCase().includes(searchWord.toLowerCase())){
return item;
}
else if(item[2].toLowerCase().includes(searchWord.toLowerCase())){
return item;
}
else if(item[5].toLowerCase().includes(searchWord.toLowerCase())){
return item;
}
})
const totalPages = Math.ceil(updatedRows.length / select);
setFilteredRows(updatedRows) //set filtered rows when `searchWord` changes
setCurrentPage(1) //reset current page to 1 if applying filters
}, [searchWord])
const renderHeadingRow = (item) => {
return <td>{item}</td>;
};
const renderRow = (item, index) => {
return (
<tr key={index}>
{
item.map((data) => {
return <td>{data}</td>;
})}
</tr>
);
};
const indexOfLast = currentPage * select;
const indexOfFirst = indexOfLast - select;
const splitData = filteredRows.slice(indexOfFirst, indexOfLast); //use `filteredRows` for display
const totalPages = Math.ceil(filteredRows.length / select); //use `filteredRows` to calculate total pages
const theadData = <tr>{heading.map(renderHeadingRow)}</tr>;
const tbodyData = displayedRows.map(renderRow);
I am trying to clear all the inputs after pressing the submit button. However, every time I click on submit, all the spaces still stay with their input values. The app is basically a sentence guesser where you have to guess the sentence displayed. If it's correct it should display the next button, and display another sentence. The app component is the following:
function App() {
const [isNextVisible, setNextVisible] = useState(false);
const [sentence, setSentence] = useState("");
const [count, setCount] = useState(1);
const [score, setScore] = useState(0);
useEffect(() => {
axios
.get(`###`)
.then((response) => {
let sentence = response.data.data.sentence;
setSentence(sentence);
})
.catch((err) => {
console.log(err);
});
}, [count, sentence]);
let mix = function (str) {
let a = str.split("");
let n = a.length;
for (let j = n - 1; j > 0; j--) {
let x = Math.floor(Math.random() * (j + 1));
let tmp = a[j];
a[j] = a[x];
a[x] = tmp;
}
return a.join("");
};
const shuffle = (str) => {
if (str.length < 2) return str;
let splitted = str.split(" ");
let converted = [];
for (let i = 0; i < splitted.length; i++) {
converted.push(mix(splitted[i]));
}
return converted.join(" ");
};
let shuffled = shuffle(sentence);
let handleSubmit = (e) => {
e.preventDefault();
setCount(count + 1);
setScore(score + 1);
};
return (
<div className="App">
<form onSubmit={handleSubmit} className="main-container">
<h1>{shuffled}</h1>
<p>Guess the sentence! Starting typing</p>
<p>The Yellow Blocks are meant for spaces</p>
<p>Score:{score}</p>
<section className="input-container">
<Input
sentence={sentence}
setNextVisible={setNextVisible}
isNextVisible={isNextVisible}
setScore={setScore}
score={score}
/>
</section>
<section>{isNextVisible ? <button>next</button> : null}</section>
</form>
</div>
);
}```
The input component is this one:
function isValidCharacter(left = null, right = null) {
return left && right && left.toUpperCase() === right.toUpperCase();
}
export default function Input(props) {
const { sentence, setNextVisible } = props;
const refFocus = React.useRef({});
const [value, setValue] = React.useState(Array(sentence.length).fill(""));
const handleChange = (index) => (event) => {
if (isValidCharacter(event.target.value, sentence[index])) {
const nextFocus = index + 1;
if (nextFocus in refFocus.current) {
refFocus.current[nextFocus].focus();
}
}
setValue((prev) => {
const next = prev.slice(0);
next[index] = event.target.value;
return next;
});
};
React.useEffect(() => {
// compare inputs with the provided word
if (value.join("").toLocaleLowerCase() === sentence.toLocaleLowerCase()) {
setNextVisible(true);
} else {
setNextVisible(false);
}
}, [value, setNextVisible, sentence]);
return (
<div style={{ display: "flex" }}>
{sentence.split("").map((character, index) => {
const hasBackgroundGreen =
value[index] === ""
? {}
: isValidCharacter(value[index], character)
? { background: "#4caf50" }
: { background: "#e1e1e1" };
return (
<input
key={index}
ref={(ref) => (refFocus.current[index] = ref)}
type="text"
value={value[index]}
onChange={handleChange(index)}
style={hasBackgroundGreen}
/>
);
})}
</div>
);
}
You do not interact with the values in the form in any way, you yourself set them value = {* value *}, so in order to change them you need:
As an option, add this to the code "Input" component
const {sentence, setNextVisible, score} = props;
useEffect(() => {
setValue(Array(sentence.length).fill(""));
}, [score])
When I define a function outside the function, I cannot access the glide parameter:
export const setFocusListenersForKeyboardNavigation = (glide) => {
const slides = glide._c.Html.slides;
for (let i = 0; i < slides.length; i++) {
const currentSlide = slides[i];
const slideButton = currentSlide.querySelector(".js-slide-button");
const slideLink = currentSlide.querySelector(".js-slide-link");
slideButton && slideButton.addEventListener('focus', focusListener);
slideLink && slideLink.addEventListener('focus', focusListener);
}
};
const focusListener = (event) => {
const activeIndex = glide._i;
const buttonIndex = event.target.dataset.slideIndex;
if (activeIndex !== parseInt(buttonIndex)) {
glide.go(`=${buttonIndex}`);
}
};
Hence, I have did something like that:
export const setFocusListenersForKeyboardNavigation = (glide) => {
const focusListener = (event) => {
const activeIndex = glide._i;
const buttonIndex = event.target.dataset.slideIndex;
if (activeIndex !== parseInt(buttonIndex)) {
glide.go(`=${buttonIndex}`);
}
};
const slides = glide._c.Html.slides;
for (let i = 0; i < slides.length; i++) {
const currentSlide = slides[i];
const slideButton = currentSlide.querySelector(".js-slide-button");
const slideLink = currentSlide.querySelector(".js-slide-link");
slideButton && slideButton.addEventListener('focus', focusListener);
slideLink && slideLink.addEventListener('focus', focusListener);
}
};
I want to know if it is hack or good practice? Is there more convenient way for doing this.
Having the function outside is better.
Mainly for readability and testing, but if your function is called a lot of times (several hundreds for example) it can be even performance hit to be redefined every time.
you can add arrow function to the listener, that will call the focusListener with the correct parameters.
you can do something like this:
export const setFocusListenersForKeyboardNavigation = (glide) => {
const slides = glide._c.Html.slides;
for (let i = 0; i < slides.length; i++) {
const currentSlide = slides[i];
const slideButton = currentSlide.querySelector(".js-slide-button");
const slideLink = currentSlide.querySelector(".js-slide-link");
slideButton && slideButton.addEventListener('focus', (event) => {focusListener(event, glide)});
slideLink && slideLink.addEventListener('focus', (event) => {focusListener(event, glide));
}
};
const focusListener = (event, glide) => {
const activeIndex = glide._i;
const buttonIndex = event.target.dataset.slideIndex;
if (activeIndex !== parseInt(buttonIndex)) {
glide.go(`=${buttonIndex}`);
}
};
I am fetching datas from server but before the call gets completed the function gets called returning an empty array.I am new to RxJs could any one help me on it
getRows: (params) => {
setTimeout(() => {
const dataAfterSortingAndFiltering = this.sortAndFilter(audits.docs, params.sortModel, params.filterModel);
const rowsThisPage = dataAfterSortingAndFiltering.slice(0, audits.items.end);
let lastRow = -1;
if (dataAfterSortingAndFiltering.length <= params.endRow) {
lastRow = dataAfterSortingAndFiltering.length;
}
params.successCallback(rowsThisPage, lastRow);
}, 3000);
sortAndFilter function:
sortAndFilter(allOfTheData, sortModel, filterModel) {
return this.sortData(sortModel, this.filterData(filterModel, allOfTheData));
}
filterData function:
filterData(filterModel, data) {
const filterKeys = Object.keys(filterModel);
const filterPresent = filterModel && Object.keys(filterModel).length > 0;
if (!filterPresent) {
return data;
}
const resultOfFilter = [];
const filterParams = [];
for (let i = 0; i < filterKeys.length; i++) {
filterParams.push(`${filterKeys[i]}=${filterModel[filterKeys[i]].filter}`);
}
const params = filterParams.join('&');
this.auditService.getColumnSearch(params).pipe(first()).subscribe((datas: any) => {
resultOfFilter.push(...datas.docs);
});
return resultOfFilter;
}
SortData function:
sortData(sortModel, data) {
console.log('sortModel got called', sortModel);
console.log('data', data);
const sortPresent = sortModel && sortModel.length > 0;
if (!sortPresent) {
return data;
}
const resultOfSort = data.slice();
resultOfSort.sort((a, b) => {
for (let k = 0; k < sortModel.length; k++) {
const sortColModel = sortModel[k];
const valueA = a[sortColModel.colId];
const valueB = b[sortColModel.colId];
if (valueA == valueB) {
continue;
}
const sortDirection = sortColModel.sort === 'asc' ? 1 : -1;
if (valueA > valueB) {
return sortDirection;
} else {
return sortDirection * -1;
}
}
return 0;
});
return resultOfSort;
}
Before the server call gets completed the sortData function returns the data as [].
Leverage the feature of async and await
export class AuditComp {
getRows(params) {
setTimeout(() => {
const dataAfterSortingAndFiltering = this.sortAndFilter(audits.docs, params.sortModel, params.filterModel);
const rowsThisPage = dataAfterSortingAndFiltering.slice(0, audits.items.end);
let lastRow = -1;
if (dataAfterSortingAndFiltering.length <= params.endRow) {
lastRow = dataAfterSortingAndFiltering.length;
}
params.successCallback(rowsThisPage, lastRow);
}, 3000);
}
sortAndFilter(allOfTheData, sortModel, filterModel) {
return this.sortData(sortModel, this.filterData(filterModel, allOfTheData));
}
sortData(sortModel, data) {
console.log('sortModel got called', sortModel);
console.log('data', data);
const sortPresent = sortModel && sortModel.length > 0;
if (!sortPresent) {
return data;
}
const resultOfSort = data.slice();
resultOfSort.sort((a, b) => {
for (let k = 0; k < sortModel.length; k++) {
const sortColModel = sortModel[k];
const valueA = a[sortColModel.colId];
const valueB = b[sortColModel.colId];
if (valueA == valueB) {
continue;
}
const sortDirection = sortColModel.sort === 'asc' ? 1 : -1;
if (valueA > valueB) {
return sortDirection;
} else {
return sortDirection * -1;
}
}
return 0;
});
return resultOfSort;
}
async filterData(filterModel, data) {
const filterKeys = Object.keys(filterModel);
const filterPresent = filterModel && Object.keys(filterModel).length > 0;
if (!filterPresent) {
return data;
}
const resultOfFilter = [];
const filterParams = [];
for (let i = 0; i < filterKeys.length; i++) {
filterParams.push(`${filterKeys[i]}=${filterModel[filterKeys[i]].filter}`);
}
const params = filterParams.join('&');
await this.auditService.getColumnSearch(params).pipe(first()).toPromise()
.then((datas: any) => {
resultOfFilter.push(...datas.docs);
});
return resultOfFilter;
}
}
comment if faced any issue.
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)]
}
}