loop through elements and set CSS styling at same time - javascript

So I want to loop through elements with a class and then loop the individual element and gradually decrease the "left" css property.
let move = document.getElementsByClassName("move");
for (var i = 0; i < move.length; i++) {
const left = move[i].getBoundingClientRect().left;
const elementId = move[i].id;
for (let j = left; j > -20; j--) {
document.getElementById(elementId).style.left = j + "%";
await new Promise(resolve => setTimeout(resolve, 20));
}
}
However, I'm using "await" to delay so that it moves slowly and doesn't zip across the screen. I want it so that all the elements have their CSS left property decrease at the same time. But instead, because of the await, the first element increases its left property and when its left property reaches the end, only then the next element goes. Please advise

While I think it would be better to use pure CSS transitions/animations for this, you could use a while loop to iterate over the elements with the move class, subtracting 1% from their left value until they are completely off screen (right <= 0).
const start = async () => {
const move = document.getElementsByClassName("move");
const canMoveLeft = () => {
const move = document.getElementsByClassName("move");
for (let i = 0; i < move.length; i++) {
if (move[i].getBoundingClientRect().right > 0) return true;
}
return false;
};
while (canMoveLeft()) {
for (let i = 0; i < move.length; i++) {
const { left, right } = move[i].getBoundingClientRect();
if (right > 0) {
const windowWidth = window.innerWidth;
const leftVal = `${Math.round((left / windowWidth) * 100) - 1}%`;
document.getElementById(move[i].id).style.left = leftVal;
}
}
await new Promise((resolve) => setTimeout(resolve, 20));
}
};

From what I understood, you want to animate left property of elements with class "move".
If that's the case you can do:
1st:
add transition property in move class
.move {
transition: left 2s;
}
2nd:
let move = document.getElementsByClassName("move");
for(let item of move) {
item.style.left = "-20%";
}

Related

Best practice to multiple 'ifs' - Javascript

I built a function to make a responsive carousel with multiples imgs per slide. (couldn't make Owl Carousel work on my Angular project, but not the point here).
I set the amount of img that will be presented by slide based on the current screen width.
Here is my code:
imgsHistoria = [
"../../assets/imgs/historia/hist_01.png",
"../../assets/imgs/historia/hist_02.png",
"../../assets/imgs/historia/hist_03.png",
"../../assets/imgs/historia/hist_04.png",
"../../assets/imgs/historia/hist_05.png",
"../../assets/imgs/historia/hist_06.png",
"../../assets/imgs/historia/hist_07.png",
"../../assets/imgs/historia/hist_08.png",
"../../assets/imgs/historia/hist_09.png",
"../../assets/imgs/historia/hist_10.png",
];
imgsHistoriaArray = [];
resizeCarousel() {
let images = this.imgsHistory;
let cut = this.getCut();
this.imgsHistoryArray = [];
for (var i = 0; i < images.length; i = i + cut) {
this.imgsHistoryArray.push(images.slice(i, i + cut));
}
}
getCut() {
if (this.getScreenWidth < 480) {
return 1
} if (this.getScreenWidth < 576) {
return 2
} if (this.getScreenWidth < 768) {
return 3
} if (this.getScreenWidth < 992) {
return 4
}
return 6;
}
The thing is that I have CodeMetrics installed and it's showing that the getCut() function has complexity 10, which is not great. How can I improve this function?
You could use an array and a loop to reduce the number of if statements:
getCut() {
let widths = [480, 576, 768, 992];
for(let i = 0; i < widths.length; i++) {
let width = widths[i];
if(this.getScreenWidth < width)
return i+1;
}
return 6;
}
Here we define an array containing the widths, loop through until we find the correct value, then return the corresponding number based on its index.

Reducing the number of times a helper function is run

I am trying to create a word cloud. In order to render text to the screen I am generating a random position for each word. This works perfectly, however there are a lot of overlapping words. In order to solve this I am storing the position and size of the elements in an array and then I created a helper function that checks for collisions, generates a new position for the element if it finds one, and then calls it's self again to check again from the start of the array. When I run my code the first 2-3 words render just fine but then I get an error saying Maximum call stack size exceeded. I saw there was already a post on this same issue on stack overflow.
I saw that the other person was using a forEach function and so was I so I converted it into a for loop like the answer suggested but it did not do anything. I think the issue boils down to the fact that there are so many collisions but I am not sure how to best approach the issue. Is there another way that I can generate unique positions for elements while still avoiding collisions?
Code:
function calculatePosition(parent, child) {
return Math.random() * parent - (child / 2)
}
// needed for rendering position of span elements
var ranges = []
var totalWidthOfWords = 0
var totalHeightOfWords = 0
// reposition element if there is a collision
function checkForCollisions(element, height, width, wordCloud, injectedSpan) {
for(var i = 0; i < ranges.length; i++) {
let current = ranges[i]
if(element.left >= current.width[0] && element.left <= current.width[1]) {
injectedSpan.style.left = calculatePosition(wordCloud.clientWidth, width) + "px";
checkForCollisions(element, height, width, wordCloud, injectedSpan)
}
if(element.top >= current.height[0] && element.top <= current.height[1]) {
injectedSpan.style.top = calculatePosition(wordCloud.clientHeight, height) + "px";
checkForCollisions(element, height, width, wordCloud, injectedSpan)
}
}
}
// Create content in DOM
const injectedContent = data.map(line => {
const injectedSpan = document.createElement("span")
const injectedWord = document.createElement("p")
const wordCloud = document.querySelector(".word-cloud")
// mod weight value to get more managable inputs
let weightValue = (line.weight * 100).toFixed(2)
// sets values of words and renders them to the screen
injectedWord.innerText = line.word
injectedSpan.appendChild(injectedWord)
wordCloud.appendChild(injectedSpan)
// sets style attribute based on weight value
injectedWord.setAttribute("style", `--i: ${weightValue}`)
// flips words
if(Math.random() > 0.75) {
injectedWord.style.writingMode = "vertical-rl";
}
// Entrance animation
let left = innerWidth * Math.random()
let top = innerHeight * Math.random()
if(Math.random() < 0.5) {
injectedWord.style.left = "-" + left + "px";
injectedSpan.style.left = calculatePosition(wordCloud.clientWidth, injectedSpan.clientWidth) + "px";
} else {
injectedWord.style.left = left + "px";
injectedSpan.style.left = calculatePosition(wordCloud.clientWidth, injectedSpan.clientWidth) + "px";
}
if(Math.random() < 0.5) {
injectedWord.style.top = "-" + top + "px";
injectedSpan.style.top = calculatePosition(wordCloud.clientHeight, injectedSpan.clientHeight) + "px";
} else {
injectedWord.style.top = top + "px";
injectedSpan.style.top = calculatePosition(wordCloud.clientWidth, injectedSpan.clientWidth) + "px";
}
// Get position of span and change coordinites if there is a collision
let spanPosition = injectedSpan.getBoundingClientRect()
console.log(spanPosition)
if(spanPosition) {
checkForCollisions(spanPosition, spanPosition.height, spanPosition.width, wordCloud, injectedSpan)
}
totalWidthOfWords += spanPosition.width
totalHeightOfWords += spanPosition.height
ranges.push({width: [spanPosition.left, spanPosition.right], height: [spanPosition.top, spanPosition.bottom]})
})
Link: https://jsfiddle.net/amotor/mdg7rzL1/4/
There is still a lot of work to do to make sure that it works properly, especially to make sure that the code does not produce any errors!
The general idea would be to follow IllsuiveBrian's comment to make sure, that checkForCollision only does the work of checking if there is a collision and that another function takes care of recalculating the position if necessary and then reevaluating a potential collision.
function checkForCollisions(element, wordCloud, injectedSpan) {
for(var i = 0; i < ranges.length; i++) {
let current = ranges[i];
// return true if there is a collision (you probably have to update the code you are using here to truly avoid collisions!)
if (collision) { return true; }
}
return false; // return false otherwise
}
Finally this part would take care of recalculating position and and rechecking for collision:
ranges.forEach(function(injectedSpan) {
// Get position of span and change coordinites if there is a collision
let spanPosition = injectedSpan.getBoundingClientRect();
if (spanPosition) {
while (checkForCollisions(spanPosition, wordCloud, injectedSpan)) {
injectedSpan.style.left = calculatePosition(wordCloud.clientWidth, element.width) + "px";
injectedSpan.style.top = calculatePosition(wordCloud.clientHeight, element.height) + "px";
}
}
});
Here is a quick idea on how to go into this direction: https://jsfiddle.net/euvbax1r/4/

What is wrong with my merge sort visualization?

I am trying to create a merge sort visualizer.
The merge sort works in the sense that if I give it an array, it'll spit back a correctly sorted array. The problem is that the "visualization" aspect of my program isn't quite working as I thought it would.
The way I attempted the implementation was that mergeSort() would take the two elements being compared, highlight them in red (no longer focusing on this for now to simplify problem), then swap the height if necessary, then turn the two elements back to the default colour:
const {
useState,
useEffect
} = React;
const numberOfBars = 8; //Number of bars shown on screen
const animationSpeed = 100; //How fast the bars animate
function SortingVisualizer() {
const [arr, setArr] = useState([]);
useEffect(() => {
newArray();
}, [])
function newArray() {
const tempArr = [];
for (let i = 0; i < numberOfBars; i++) {
tempArr.push(Math.floor(Math.random() * 100) + 5)
}
setArr(tempArr);
}
return (
<div id = "main-container" >
<button id = "new-array-button" onClick = {() => newArray()}> New Array </button>
<button id = "merge-sort-button" onClick = {() => mergeSort(arr)}> Merge Sort < /button>
<div id = "bar-container" >
{
arr.map((value, index) => (
<div className = 'bar'
key = {
index
}
style = {
{
backgroundColor: "aquamarine",
height: value + "px"
}
}
/>
))
}
</div>
</div >
)
}
// Recursively breaks down unsortedArray into lowIndex, midIndex, highIndex and pass to mergeSortHelper()
// Initialized aux, lowIndex and highIndex in argument.
function mergeSort(unsortedArray, aux = [...unsortedArray], lowIndex = 0, highIndex = unsortedArray.length - 1) {
// Base case
if (highIndex === lowIndex) return;
// Get midIndex
const midIndex = Math.floor((highIndex + lowIndex) / 2);
// Recursively run left side and right side until base case reached.
mergeSort(unsortedArray, aux, lowIndex, midIndex);
mergeSort(unsortedArray, aux, midIndex + 1, highIndex);
// Merge the left sides and right sides
mergeSortHelper(unsortedArray, aux, lowIndex, midIndex, highIndex);
}
// Does the work of sorting list and animating the height swap
function mergeSortHelper(unsortedArray, aux, lowIndex, midIndex, highIndex) {
let auxkey = lowIndex;
let i = lowIndex;
let j = midIndex + 1;
let barI = undefined;
let barJ = undefined;
// While there are elements in left/right sides, put element in auxillary array
// then increment the indexes by 1.
while (i <= midIndex && j <= highIndex) {
let arrayBars = document.getElementsByClassName('bar');
barI = arrayBars[i].style;
barJ = arrayBars[j].style;
if (unsortedArray[i] <= unsortedArray[j]) {
aux[auxkey] = unsortedArray[i];
auxkey++;
i++;
} else {
let tmpHeight = barJ.height; //swaps value at index i and j
barJ.height = barI.height; //swaps value at index i and j
barI.height = tmpHeight; //swaps value at index i and j
aux[auxkey] = unsortedArray[j];
auxkey++;
j++;
}
}
// Move any remaining elements from unsortedArray into auxillary array.
while (i <= midIndex) {
aux[auxkey] = unsortedArray[i];
auxkey++;
i++;
}
// Copy the auxillary array to the unsortedArray so that in the next iteration, the unsortedArray will reflect the new array
for (let k = 0; k <= highIndex; k++) {
unsortedArray[k] = aux[k];
}
}
ReactDOM.render( < SortingVisualizer / > , document.querySelector("#app"))
#new-array-button {
position: absolute;
}
#merge-sort-button {
position: absolute;
left: 100px;
}
#bar-container {
align-items: flex-end;
background-color: lightgray;
display: flex;
height: 200px;
justify-content: center;
}
.bar {
margin: 0 2px;
width: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
I've been trying to implement the color change and height swap in different parts of my program but it never seems to work correctly. Where am I going wrong with the visualization?
Removed my jsfiddle link from the question as I am using the on-site code snippet now. But in case anyone still wants it, it's here: https://jsfiddle.net/SushiCode/k0954yep/17/

Dynamically create any number of sequentially firing functions

(JavaScript) I have a function that deals player cards in a nice sequential fashion: Player, Dealer, Player, Dealer. Below is that part of the function which sequentially moves cards to the viewport.
setTimeout(()=>{
player1.style.top = `70%`;
player1.style.left = `30%`;
player1.style.transform = `rotate(${Math.floor(Math.random() * rotationMax)}deg)`;
setTimeout(() => {
dealer1.style.top = `8%`;
dealer1.style.left = `30%`
dealer1.style.transform = `rotate(${Math.floor(Math.random() * rotationMax)+1}deg)`;
setTimeout(() => {
player2.style.top = `70%`;
player2.style.left = `50%`
player2.style.transform = `rotate(${Math.floor(Math.random() * rotationMax)}deg)`;
setTimeout(() => {
flippedCard.style.top = '8%';
flippedCard.style.left = '44%';
}, 200)}, 200)}, 100)}, 200)
You can see that this block works only with a set number of cards (in this case 4). I am not yet good enough in Javascript to create function that would dynamically generate any number of cards to be dealt.
Can someone point me in the right direction? Specific question: how do you dynamically generate tasks that run one after another.
Make an array of dealer cards and player cards, and figure out the differences in the left you want for each. Then iterate over the arrays, delaying with await to make the code flat and readable:
const delay200 = () => new Promise(res => setTimeout(res, 200);
const playerCards = [player1, player2];
const dealerCards = [dealer1, dealer2];
const playerLeftIncrement = 20; // eg: 30%, then 50%, then 70%; adjust as needed
const dealerLeftIncrement = 14; // eg: 30%, then 44%, then 58%; adjust as needed
const applyStyle = (card, left) => {
Object.assign(
card.style,
{
top: '70%',
left,
transform: `rotate(${Math.floor(Math.random() * rotationMax)}deg)`,
}
);
};
for (let i = 0; i < playerCards.length; i++) {
applyStyle(playerCards[i], `${30 + i * playerLeftIncrement}%`);
await delay200();
applyStyle(dealerCards[i], `${30 + i * dealerLeftIncrement}%`);
await delay200();
}
It would be useful to have a function that looks something like:
callFunctionsWithDelays(functions, delays)
That would avoid the nested look to your code, and make it easy to dynamically generate. I'd write this using async/await syntax:
async function callFunctionsWithDelays(functions, delays) {
for (i = 0; i < functions.length; i++) {
functions[i].call()
await new Promise(resolve, setTimeout(resolve, delays[i]))
}
}

Javascript - Merge Sort Visualizer using CSS Style to Sort, having issues

I am having an issue with my merge sort visualizer.
My program has no issues visualizing bubble sort or quick sort, as I can do the swapping operation of css property values in-place, but I am having major issues trying to get merge sort to work properly. The issue arises when I try to update a css property on the dom, it causes the sort to not function.
I have tried passing in copies of the data I wish to sort, and all sorts of weird things I could think of to make it work. I am currently trying to sort by the css property 'maxWidth'. I use that to display how large a div element is in the html file and then visualize the sort from there.
My latest thought has been to set all the div elements to have another css property equal to the maxWidth (I am using fontSize as it does not affect my program) and then sorting based on fontSize, allowing me in theory to change the maxWidth properties of the divs without affecting merge sorts algorithm.
I am including my entire js file as I hope reading my correctly working bubble sort or quick sort functions can help you see what I am trying to achieve. Thank you so much for taking the time to read this and offer any help!
Important Note: I am not trying to visualize the individual steps of merge sort yet because I am unable to update the final result to the html page without affecting the merge sort algorithm. According to console logs, my merge sort algorithm does indeed work, I just can't update the DOM without messing it up. Once I can do that, I will turn it into an asynchronous function using async and await like I previously did with bubble and quick sort.
/********* Generate and Store Divs to be Sorted *************/
const generateSortingDivs = (numOfDivs) => {
const divContainer = document.querySelector('.div-container');
let html = '';
for (let i = 0; i < numOfDivs; i++) {
let r = Math.floor(Math.random() * 100);
html += `<div class='sorting-div' id='id-${i}' style='max-width: ${r}%'>&nbsp</div>`;
}
divContainer.innerHTML = html;
for(let i = 0; i < numOfDivs; i++) {
let x = document.getElementById('id-' + i);
x.style.fontSize = x.style.maxWidth;
}
}
const storeSortingDivs = () => {
const divContainer = document.querySelector('.div-container');
let divCollection = [];
const numOfDivs = divContainer.childElementCount;
for(let i=0; i<numOfDivs; i++) {
let div = document.getElementById('id-' + i);
divCollection.push(div);
}
return divCollection;
}
/********** SLEEP FUNCTION ************/
//Used to allow asynchronous visualizations of synchronous tasks
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/******* SWAP FUNCTIONS *********/
//Used for Testing Algorithm before Animating Visualization
const syncSwap = (div1, div2) => {
let tmp = div1.style.maxWidth;
div1.style.maxWidth = div2.style.maxWidth;
div2.style.maxWidth = tmp;
}
async function asyncSwap(div1, div2) {
await sleep(50);
let tmp = div1.style.maxWidth;
div1.style.maxWidth = div2.style.maxWidth;
div2.style.maxWidth = tmp;
}
const swapDivs = (smallerDiv, biggerDiv) => {
return new Promise(resolve => {
setTimeout(() => {
let tmp = smallerDiv.style.maxWidth;
smallerDiv.style.maxWidth = biggerDiv.style.maxWidth;
biggerDiv.style.maxWidth = tmp;
resolve();
}, 50);
});
}
/****************************************/
/*********** SORTING ALGO'S *************/
/****************************************/
/******* BUBBLE SORT ***********/
async function bubbleSort(divCollection) {
displayBubbleSortInfo();
const len = divCollection.length;
for(let i=0; i<len; i++) {
for(let j=0; j<len-i-1; j++) {
divCollection[j].style.backgroundColor = "#FF4949";
divCollection[j+1].style.backgroundColor = "#FF4949";
let numDiv1 = parseInt(divCollection[j].style.maxWidth);
let numDiv2 = parseInt(divCollection[j+1].style.maxWidth);
let div1 = divCollection[j];
let div2 = divCollection[j+1];
if(numDiv1 > numDiv2) {
await swapDivs(div2, div1);
}
divCollection[j].style.backgroundColor = "darkcyan";
divCollection[j+1].style.backgroundColor = "darkcyan";
}
divCollection[len - i - 1].style.backgroundColor = 'black';
}
}
function displayBubbleSortInfo(){
const infoDiv = document.querySelector('.algo-info');
let html = `<h1>Bubble Sort Visualizer</h1>`;
html += `<h2>Time Complexity: O(n^2)</h2>`;
html += `<h3>Space Complexity: O(1)</h3>`;
html += `<p>This sorting algorithm loops through the array and continues to push the
largest found element into the last position, also pushing the last available
position down by one on each iteration. It is guaranteed to run in exactly
O(n^2) time because it is a nested loop that runs completely through.</p>`;
infoDiv.innerHTML = html;
}
/****** QUICK SORT ********/
async function quickSort(divCollection, start, end) {
if(start >= end) return;
let partitionIndex = await partition(divCollection, start, end);
await Promise.all([quickSort(divCollection, start, partitionIndex - 1), quickSort(divCollection, partitionIndex + 1, end)]);
}
/* This function takes last element as pivot, places
the pivot element at its correct position in sorted
array, and places all smaller (smaller than pivot)
to left of pivot and all greater elements to right
of pivot */
async function partition(divCollection, start, end) {
let pivotIndex = start;
let pivotValue = parseInt(divCollection[end].style.maxWidth);
for(let i = start; i < end; i++) {
if(parseInt(divCollection[i].style.maxWidth) < pivotValue) {
await asyncSwap(divCollection[i], divCollection[pivotIndex]);
pivotIndex++;
}
}
await asyncSwap(divCollection[pivotIndex], divCollection[end]);
return pivotIndex;
}
function displayQuickSortInfo(){
const infoDiv = document.querySelector('.algo-info');
let html = `<h1>Quick Sort Visualizer</h1>`;
html += `<h2>Time Complexity: O(n log n)</h2>`;
html += `<h3>Space Complexity: O(log n)</h3>`;
html += `<p>This sorting algorithm uses the idea of a partition to sort
each iteration recursively. You can implement quick sort
in a variety of manners based on the method in which you
pick your "pivot" value to partition the array. In this
visualization, I implemented the method that chooses the
last element of the array as the pivot value. You could
also choose the first value, the middle value, or the median
value based on the first, middle, and last values.</p>`;
infoDiv.innerHTML = html;
}
/* Merge Sort does not sort in place, and thus we have to be
* clever when implementing it and also editing the css style
* of our divs to show the visualization of how the algorithm
* works. My method is to store a copy of the divs, that way
* I can use one to be sorted by merge sort, and the other to
* change the css style property to show the visualization.
* Unlike Quick Sort and Bubble Sort, we are not swapping
* elements when sorting, instead we are merging entire
* arrays together as the name implies. */
function mergeSort(divCollection) {
if(divCollection.length < 2) return divCollection;
let middleIndex = Math.floor(divCollection.length / 2);
let left = divCollection.slice(0, middleIndex);
let right = divCollection.slice(middleIndex);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
let mergedCollection = [];
while(left.length && right.length) {
if(parseInt(left[0].style.fontSize) < parseInt(right[0].style.fontSize || right.length === 0)) {
let el = left.shift();
mergedCollection.push(el);
} else {
let el = right.shift();
mergedCollection.push(el);
}
}
let res = mergedCollection.concat(left.slice().concat(right.slice()));
return res;
}
/***** INITIALIZATION FUNCTION *******/
generateSortingDivs(10);
let divs = storeSortingDivs();
let copyDivs = [...divs];
console.log('Original State: ')
console.log(divs);
//bubbleSort(divs);
//displayQuickSortInfo();
//quickSort(divs, 0, divs.length-1);
let x = mergeSort(copyDivs);
console.log('Sorted: ');
console.log(x);

Categories

Resources