What is wrong with my merge sort visualization? - javascript

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/

Related

react components does not refresh

I'm making a react app where i have to update a list twice (at least or more). Obviously i have to use useState hook for being able to edit that list whenever i want. That's pretty easy, but the problem is that i don't really know but in my app, the two lists fusion themselves. Here's an example: i have a fruit list ["apples", "bananas", "avocados"] and a social network one ["instagram", "snapchat", "facebook", "whatsapp", "telegram"]. Now, when i change the state to the first list it becomes exactly: ["apples", "bananas", "avocados", "whatsapp", "telegram"]. And that's really weird. Instead of a set it makes a somelike difference(?). By the way, here's some simplified code of what i wrote (the essential):
// Initialize in the App function
const [colors, setColors] = useState([])
const [colorpicker,setColorPicker] = useState(null)
const [inputskin, setInputSkin] = useState(localStorage.getItem('skin') !== null ? localStorage.getItem('skin') : testskin)
const inputFile = useRef(null) // rename as inputSkin
useEffect(() => {
const allcolors = []
async function getPixels(){
console.log('changed input skin')
const image = await pixels(inputskin)
for(let i = 0; i < image.height; i++){
pixelLoop:
for(let j = 0; j < image.width; j++){
const color = []
for(let k = 0; k < 4; k++) {
color.push(image.data[(i * image.width + j) * 4 + k])
}
const hex = rgbToHex(color)
for (let clr of allcolors) if (clr[0] === hex) { clr[1]++; continue pixelLoop }
if (!(allcolors.includes(hex)) && color[3] != 0) allcolors.push([hex,0])
}
}
allcolors.sort((a,b) => b[1] - a[1])
setColors(allcolors)
console.log(allcolors)
}
getPixels()
changePalette = setColorPicker
}, [inputskin])
I really can't figure out what's the problem because when i console log the colors array, that's okay, it shows me the array that i want to have in the render, but in the render it basically does not refresh itself. I tried removing by DOM the children of the components nodes, but this does not really help. Here's how i render the components:
// render
return(
...
{ colors.map(([color], i) => <Color colorstart = { color } id = { i } key = { i } colorChange = { colorChange } />) }
...
)
Here's when i load the first color array:
And here's when i load the second color array:
Here's what i would expect to have when i load the second array (I obtain this only if i reload the page):
As you can see it eats the first two color rows :(
You are not setting key property in the map function which probably causes the problem. Set unique key property in
<Color colorstart = { color } id = { i } colorChange = { colorChange } />
for example
<Color key={color[0]} colorstart = { color } id = { i } colorChange = { colorChange } />
and check if it helps.
Read more here: https://reactjs.org/docs/lists-and-keys.html

loop through elements and set CSS styling at same time

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%";
}

javaScript minesweeper empty fields reveal

I am trying to create a minesweeper game using html css and js. I created the board, the fields(div elements), planted the bombs, every field "knows" his position (x,y) (the x and y coordinates are saved as data attributes of the div), "knows" if he is a bomb and the number of bombs nearby. I managed to create onclick event to reveal if the tile is a bomb or to show the number of bombs nearby. Now my problem is that if i click on a tile which has no bombs nearby (empty tile) i want to reveal all empty tiles which are in the surrounding.
So the next function is called if i click on an empty field:
function zero(x, y) {
for (var i = x - 1; i <= x + 1; i++) {
for (var j = y - 1; j <= y + 1; j++) {
$('.tile[row-d=' + i + '][col-d=' + j + ']').text($('.tile[row-d=' + i + '][col-d=' + j + ']').attr('bomb_number')); // set the field text = bomb -number nearby
}
}
}
With this i reveal all the surrounding fields near the clicked empty field, so i reveal just 9 fields. I want to reveal all empty fields nearby not just 9. I tried to use recursive function but the page crashed.
So anybody can help me with a recursive function which could solve this. Thank you. (As you can see i am also using jQuery)
The basic rule is:
If you find a neighboring field that is next to a bomb, stop searching
If a neighboring field has no bomb, keep expanding to the new field's neighbors
In code:
function zero(x, y) {
for (var i = x - 1; i <= x + 1; i++) {
for (var j = y - 1; j <= y + 1; j++) {
var neighbor = $('.tile[row-d=' + i + '][col-d=' + j + ']');
var bombNr = neighbor.attr('bomb_number');
neighbor.text(bombNr);
if (bombNr === "0") zero(i, j);
}
}
}
Edit: a quick & dirty example of the recursion pattern:
// The recursive function
const handleInput = i => {
if (tapped.has(i)) return;
// Count the bombs in neighboring tiles
const bombs = neighbors(i)
.map(j => board[j])
.filter(v => v === x)
.length;
// Store the count so we can render it
tapped.set(i, bombs);
// If there are no bombs, handle all the neighbors'
// as well.
if (bombs === 0) {
neighbors(i).forEach(handleInput);
}
};
// Game state
const x = "x";
const board = [
x, 0, 0, 0, 0,
x, 0, 0, 0, 0,
x, x, x, 0, 0,
0, 0, 0, 0, 0,
0, 0, x, 0, 0
];
const tapped = new Map();
// Quick & dirty render method to visualize what happens
const render = board => {
const el = document.querySelector(".board")
el.innerHTML =
board
.map((v, i) => tapped.has(i) ? tapped.get(i) : v)
.map(txt => `<div>${txt}</div>`)
.join("");
Array.from(el.children).forEach((c, i) => {
c.addEventListener("click", () => {
if (board[i] === x) return console.log("Boom!");
handleInput(i);
render(board);
});
c.classList.toggle("tapped", tapped.has(i));
});
}
const row = i => i / 5 << 0;
const col = i => i % 5;
const neighbors = i => {
const top = [ -6, -5, -4 ];
const right = [ -4, 1, 6 ];
const bottom = [ 4, 5, 6 ];
const left = [ -6, -1, 4 ];
const ds = new Set([...top, ...right, ...bottom, ...left]);
const remove = d => ds.delete(d);
if (row(i) === 0) top.forEach(remove);
if (row(i) === 4) bottom.forEach(remove);
if (col(i) === 0) left.forEach(remove);
if (col(i) === 4) right.forEach(remove);
return [...ds].map(d => d + i);
};
render(board);
.board {
width: 100px;
height: 100px;
}
.board > div {
width: 20px;
height: 20px;
border: 1px solid black;
box-sizing: border-box;
display: inline-block;
text-align: center;
}
.board > div.tapped {
background: yellow;
}
<div class="board"></div>

How to efficiently track rolling min / max values in a frequently updated sliding array

Consider the following javascript data structure:
let sensors = {
sensor1: {
min: 1.00,
max: 9.00,
data: [
{
timestamp: 1517760374400,
value: 1.00
},
{
timestamp: 1517760374500,
value: 2.00
},
{
timestamp: 1517760374600,
value: 9.00
},
{
timestamp: 1517760374700,
value: 1.00
},
{
timestamp: 1517760374800,
value: 3.00
},
{
timestamp: 1517760374900,
value: 1.00
},
{
timestamp: 1517760375000,
value: 9.00
},
{
timestamp: 1517760375100,
value: 8.00
},
]
},
// sensor2, sensor3, etc...
}
Imagine there could be thousands of timestamped data for each sensor.
Initially you can easily set a min / max value, every time an object is added by checking if it is bigger or smaller than the current max
But the tricky part and my question is this:
What is the size of the array is limited - in this case we would set it to a length of 8.
Whenever a new item after item 8 is added (the limit is reached), the 1st item will be removed, and the nth item will be pushed into the end of the array.
The problem is that there can be more items with the same value, and even if there isn't we have no way of knowing which min / max is next without iterating the entire array once again
This is supposed to be scalable to thousands of array items, and is to be run roughly every second with ideally - as low cpu utilization as possible - I don't really think looping over thousands of items every second will be effecient enough.
Do you see another other ways of keeping track of min / max values of an array which is changing like this ever second?
Data structure:
Queue size of N to store N item.
Min / Max Heap to track the min / max item.
A hash map to track the frequency of each item.
When you there is a coming data, update the frequency of the new item, if not there in the heap, add it.
When you need to pop an item, decrease the frequency, while frequency of head == 0, remove from the heap.
Head of the heap is the solution.
Pseudo code:
const swap = (data, i, j) => {
let temp = data[i];
data[i] = data[j];
data[j] = temp;
}
class Heap {
constructor() {
this.data = [];
this.inHeap = {};
this.size = 0;
}
head() {
return this.data[0];
}
// add item O(logN);
add(number) {
if (!this.inHeap[number]) {
this.data[this.size++] = number;
let current = this.size - 1;
while (current > 0) {
if (this.data[current >> 1] < this.data[current]) {
swap(this.data, current >> 1, current);
current >>= 1;
} else {
break;
}
}
this.inHeap[number] = true;
}
}
// remove head O(logN);
remove() {
this.size--;
delete this.inHeap[this.data[0]];
this.data[0] = this.data[this.size];
let current = 0;
while (current * 2 + 1 < this.size) {
let next = current * 2 + 1;
if (current * 2 + 2 < this.size && this.data[current * 2 + 2] > this.data[current * 2 + 1]) {
next = current * 2 + 2;
}
if (this.data[current] < this.data[next]) {
swap(this.data, current, next);
current = next;
} else {
break;
}
}
}
}
class Queue {
constructor(maxSize) {
this.maxSize = maxSize;
this.size = 0;
this.data = [];
this.head = -1;
}
// add a number and return the removed item if any
add(number) {
let next = (this.head + 1) % this.maxSize;
let removedItem = this.data[next];
this.data[next] = number;
this.head = next;
if (removedItem === undefined) {
this.size++;
}
return removedItem;
}
get(i) {
return this.data[(this.head - this.size + 1 + i + this.maxSize ) % this.maxSize];
}
}
class Solution {
constructor(n) {
this.n = n;
this.queue = new Queue(this.n);
this.heap = new Heap();
this.frequency = {};
}
add(number) {
let removedItem = this.queue.add(number);
if (!this.frequency[number]) {
this.frequency[number] = 1;
this.heap.add(number);
} else {
this.frequency[number]++;
}
if (removedItem !== undefined) {
this.frequency[removedItem]--;
if (!this.frequency[removedItem]) {
delete this.frequency[removedItem];
}
// remove head if frequency is zero
while (!this.frequency[this.heap.head()]) {
this.heap.remove();
}
}
}
size() {
return this.queue.size;
}
get(i) {
return this.queue.get(i);
}
max() {
return this.heap.head();
}
}
/*** use of solution here!! **/
let solution = new Solution(3);
let numberInput = document.getElementById("number");
let data = document.getElementById("data");
let maxResult = document.getElementById("max");
let heapData = document.getElementById("heap");
let queueData = document.getElementById("queue");
let frequencyData = document.getElementById("frequency");
function addNumber() {
let value = parseInt(numberInput.value);
if (isNaN(value)) {
alert("Please input a number!");
} else {
solution.add(value);
}
maxResult.innerHTML = "Max: " + solution.max();
// gather data
let dataString = "";
for (let i = 0; i < solution.size(); i++) {
dataString += " " + solution.get(i);
}
data.innerHTML = "Data: " + dataString;
heapData.innerHTML = "Heap: " + JSON.stringify(solution.heap.data.slice(0, solution.heap.size));
queueData.innerHTML = "Queue: " + JSON.stringify(solution.queue);
frequencyData.innerHTML = "Frequency: " + JSON.stringify(solution.frequency);
numberInput.value = parseInt(Math.random() * 1000);
}
.input {
display: flex;
}
.input input {
width: 200px;
padding: 5px 10px;
outline: none;
}
.input button {
padding: 5px 10px;
border: 1px solid light gray;
}
div {
padding: 5px 10px;
}
<div class="input">
<input type="text" id="number" />
<button onClick="addNumber()">Add</button>
</div>
<div class="result">
<div class="data" id="data">
Data:
</div>
<div class="max" id="max">
Max: undefined!
</div>
</div>
<div class="debug">
<div>
<code class="data" id="heap">
Heap:
</code>
</div>
<div>
<code class="max" id="queue">
Queue:
</code>
</div>
<div>
<code class="max" id="frequency">
Frequency:
</code>
</div>
</div>
Sounds fun. I think that you're going to run into a problem where you just don't know a priori whether a value is going to be an extreme (max/min) value in the future.
My thought would be to add an expiration counter to your current max & min values. This counter decrements each time you don't replace your rolling min/max. It is reset to 8 when it is updated with a new, fresh value. Min and max obviously have separate counters.
Now you only have to loop through your values if a counter decrements to 0 and your min/max value becomes stale (it is no longer in your list). If, for example, your min counter expires, you now have to identify the remaining minimum that is currently in your list of 8. In this case, you'll reset the expiration counter to match the number of iterations until that new min value gets removed from the list (8 - it's index).
That might save you some CPU cycles!

How can you figure out the highest z-index in your document?

In order to set a div containing a transparent text image as the highest z-index in my document, I picked the number 10,000 and it solved my problem.
Previously I had guessed with the number 3 but it had no effect.
So, is there a more scientific way of figuring out what z-index is higher than that of all of your other elements?
I tried looking for this metric in Firebug but couldn't find it.
Stealing some code from abcoder site for the sake of clarity:
var maxZ = Math.max.apply(null,
$.map($('body *'), function(e,n) {
if ($(e).css('position') != 'static')
return parseInt($(e).css('z-index')) || 1;
}));
You could call findHighestZIndex for a particular element type such as <div> like this:
findHighestZIndex('div');
assuming a findHighestZindex function that is defined like this:
function findHighestZIndex(elem)
{
var elems = document.getElementsByTagName(elem);
var highest = Number.MIN_SAFE_INTEGER || -(Math.pow(2, 53) - 1);
for (var i = 0; i < elems.length; i++)
{
var zindex = Number.parseInt(
document.defaultView.getComputedStyle(elems[i], null).getPropertyValue("z-index"),
10
);
if (zindex > highest)
{
highest = zindex;
}
}
return highest;
}
Using ES6 a cleaner approach
function maxZIndex() {
return Array.from(document.querySelectorAll('body *'))
.map(a => parseFloat(window.getComputedStyle(a).zIndex))
.filter(a => !isNaN(a))
.sort()
.pop();
}
I’d like to add my ECMAScript 6 implementation that I use in one of my UserScripts. I’m using this one to define the z-index of specific elements so that they always appear the highest.
In JS, you can additionally set certain attributes or class names to elements that you may want to exclude. For instance, consider your script setting a data-highest attribute on an element that you want to appear as the highest element (e.g. a popup); and consider an element with class name yetHigher that you don’t control, which should be even higher (e.g. a custom context menu). I can exclude these elements with a chained :not selector. Note that :not([data-highest], .yetHigher) is possible, but experimental, and only has limited browser support as of January 2021.
let highestZIndex = 0;
// Then later, potentially repeatedly
highestZIndex = Math.max(
highestZIndex,
...Array.from(document.querySelectorAll("body *:not([data-highest]):not(.yetHigher)"), (elem) => parseFloat(getComputedStyle(elem).zIndex))
.filter((zIndex) => !isNaN(zIndex))
);
The lower five lines can run multiple times and update the variable highestZIndex repeatedly by finding out the maximum between the current highestZIndex value and all the other computed z-indexes of all elements. The filter excludes all the "auto" values.
I believe what you are observing is Voodoo. Without access to your complete style sheet I can of course not tell reliably; but it strikes me as likely that what really has happened here is that you have forgotten that only positioned elements are affected by z-index.
Additionally, z-indexes aren't assigned automatically, only in style sheets, which means that with no other z-indexed elements, z-index:1; will be on top of everything else.
I guess you have to do this yourself ...
function findHighestZIndex()
{
var divs = document.getElementsByTagName('div');
var highest = 0;
for (var i = 0; i < divs .length; i++)
{
var zindex = divs[i].style.zIndex;
if (zindex > highest) {
highest = zindex;
}
}
return highest;
}
There isn't a default property or anything, but you could write some javascript to loop through all elements and figure it out. Or if you use a DOM management library like jQuery, you could extend its methods (or find out if it supports it already) so that it starts tracking element z-indices from page load, and then it becomes trivial to retrieve the highest z-index.
The "ES6" version above is less efficient than the first solution because it does multiple redundant passes across the full array. Instead try:
findHighestZ = () =>
[...document.querySelectorAll('body *')]
.map(elt => parseFloat(getComputedStyle(elt).zIndex))
.reduce((highest, z) => z > highest ? z : highest, 1)
In theory it would be even quicker to do it in one reduce step, but some quick benchmarking showed no significant difference, and the code is gnarlier
The best way to solve this problem is, in my opinion, just to set yourself conventions for what kinds of z-indexes are used for different kinds of elements. Then, you'll find the correct z-index to use by looking back at your documentation.
Using jQuery:
if no elements supplied, it checks all elements.
function maxZIndex(elems)
{
var maxIndex = 0;
elems = typeof elems !== 'undefined' ? elems : $("*");
$(elems).each(function(){
maxIndex = (parseInt(maxIndex) < parseInt($(this).css('z-index'))) ? parseInt($(this).css('z-index')) : maxIndex;
});
return maxIndex;
}
I had to do this for a project recently, and I found that I benefitted a lot from #Philippe Gerber's great answer here, and #flo's great answer (the accepted answer).
The key differences to the answers referenced above are:
Both the CSS z-index, and any inline z-index style are calculated, and use the larger of the two for comparison and calculation.
Values are coerced into integers, and any string values (auto, static, etc) are ignored.
Here is a CodePen for the code example, but it's included here as well.
(() => {
/**
* Determines is the value is numeric or not.
* See: https://stackoverflow.com/a/9716488/1058612.
* #param {*} val The value to test for numeric type.
* #return {boolean} Whether the value is numeric or not.
*/
function isNumeric(val) {
return !isNaN(parseFloat(val)) && isFinite(val);
}
/**
* Finds the highest index in the current document.
* Derived from the following great examples:
* [1] https://stackoverflow.com/a/1118216/1058612
* [2] https://stackoverflow.com/a/1118217/1058612
* #return {number} An integer representing the value of the highest z-index.
*/
function findHighestZIndex() {
let queryObject = document.querySelectorAll('*');
let childNodes = Object.keys(queryObject).map(key => queryObject[key]);
let highest = 0;
childNodes.forEach((node) => {
// Get the calculated CSS z-index value.
let cssStyles = document.defaultView.getComputedStyle(node);
let cssZIndex = cssStyles.getPropertyValue('z-index');
// Get any inline z-index value.
let inlineZIndex = node.style.zIndex;
// Coerce the values as integers for comparison.
cssZIndex = isNumeric(cssZIndex) ? parseInt(cssZIndex, 10) : 0;
inlineZIndex = isNumeric(inlineZIndex) ? parseInt(inlineZIndex, 10) : 0;
// Take the highest z-index for this element, whether inline or from CSS.
let currentZIndex = cssZIndex > inlineZIndex ? cssZIndex : inlineZIndex;
if ((currentZIndex > highest)) {
highest = currentZIndex;
}
});
return highest;
}
console.log('Highest Z', findHighestZIndex());
})();
#root {
background-color: #333;
}
.first-child {
background-color: #fff;
display: inline-block;
height: 100px;
width: 100px;
}
.second-child {
background-color: #00ff00;
display: block;
height: 90%;
width: 90%;
padding: 0;
margin: 5%;
}
.third-child {
background-color: #0000ff;
display: block;
height: 90%;
width: 90%;
padding: 0;
margin: 5%;
}
.nested-high-z-index {
position: absolute;
z-index: 9999;
}
<div id="root" style="z-index: 10">
<div class="first-child" style="z-index: 11">
<div class="second-child" style="z-index: 12"></div>
</div>
<div class="first-child" style="z-index: 13">
<div class="second-child" style="z-index: 14"></div>
</div>
<div class="first-child" style="z-index: 15">
<div class="second-child" style="z-index: 16"></div>
</div>
<div class="first-child" style="z-index: 17">
<div class="second-child" style="z-index: 18">
<div class="third-child" style="z-index: 19">
<div class="nested-high-z-index">Hello!!! </div>
</div>
</div>
</div>
<div class="first-child">
<div class="second-child"></div>
</div>
<div class="first-child">
<div class="second-child"></div>
</div>
<div class="first-child">
<div class="second-child"></div>
</div>
</div>
Array.reduce()
Here's another solution to determine the topmost z-index that uses Array.reduce():
const max_zindex = [...document.querySelectorAll('body *')].reduce((accumulator, current_value) => {
current_value = +getComputedStyle(current_value).zIndex;
if (current_value === current_value) { // Not NaN
return Math.max(accumulator, current_value)
}
return accumulator;
}, 0); // Default Z-Index Rendering Layer 0 (Zero)
ShadowRoot solutions
We must not forget about custom-elements and shadow-root content.
function computeMaxZIndex() {
function getMaxZIndex(parent, current_z = 0) {
const z = parent.style.zIndex != "" ? parseInt(parent.style.zIndex, 10) : 0;
if (z > current_z)
current_z = z;
const children = parent.shadowRoot ? parent.shadowRoot.children : parent.children;
for (let i = 0; i < children.length; i++) {
const child = children[i];
current_z = getMaxZIndex(child, current_z);
}
return current_z;
}
return getMaxZIndex(document.body) + 1;
}
If you're looking to show the IDs of all elements with the highest z indices:
function show_highest_z() {
z_inds = []
ids = []
res = []
$.map($('body *'), function(e, n) {
if ($(e).css('position') != 'static') {
z_inds.push(parseFloat($(e).css('z-index')) || 1)
ids.push($(e).attr('id'))
}
})
max_z = Math.max.apply(null, z_inds)
for (i = 0; i < z_inds.length; i++) {
if (z_inds[i] == max_z) {
inner = {}
inner.id = ids[i]
inner.z_index = z_inds[i]
res.push(inner)
}
}
return (res)
}
Usage:
show_highest_z()
Result:
[{
"id": "overlay_LlI4wrVtcuBcSof",
"z_index": 999999
}, {
"id": "overlay_IZ2l6piwCNpKxAH",
"z_index": 999999
}]
A solution highly inspired from the excellent idea of #Rajkeshwar Prasad .
/**
returns highest z-index
#param {HTMLElement} [target] highest z-index applyed to target if it is an HTMLElement.
#return {number} the highest z-index.
*/
var maxZIndex=function(target) {
if(target instanceof HTMLElement){
return (target.style.zIndex=maxZIndex()+1);
}else{
var zi,tmp=Array.from(document.querySelectorAll('body *'))
.map(a => parseFloat(window.getComputedStyle(a).zIndex));
zi=tmp.length;
tmp=tmp.filter(a => !isNaN(a));
return tmp.length?Math.max(tmp.sort((a,b) => a-b).pop(),zi):zi;
}
};
#layer_1,#layer_2,#layer_3{
position:absolute;
border:solid 1px #000;
width:100px;
height:100px;
}
#layer_1{
left:10px;
top:10px;
background-color:#f00;
}
#layer_2{
left:60px;
top:20px;
background-color:#0f0;
z-index:150;
}
#layer_3{
left:20px;
top:60px;
background-color:#00f;
}
<div id="layer_1" onclick="maxZIndex(this)">layer_1</div>
<div id="layer_2" onclick="maxZIndex(this)">layer_2</div>
<div id="layer_3" onclick="maxZIndex(this)">layer_3</div>
Robust solution to find maximum zIndex in NodeList
You should check both getComputedStyle and style object provided by node itself
Use Number.isNaN instead of isNaN because of isNaN("") === false
function convertToNumber(value) {
const asNumber = parseFloat(value);
return Number.isNaN(asNumber) ? 0 : asNumber;
}
function getNodeZIndex(node) {
const computedIndex = convertToNumber(window.getComputedStyle(node).zIndex);
const styleIndex = convertToNumber(node.style.zIndex);
if (computedIndex > styleIndex) {
return computedIndex;
}
return styleIndex;
}
function getMaxZIndex(nodeList) {
const zIndexes = Array.from(nodeList).map(getNodeZIndex);
return Math.max(...zIndexes);
}
const maxZIndex = getMaxZIndex(document.querySelectorAll("body *"));
Short
[...document.querySelectorAll`*`]
.reduce((a,e,i,t,z=+window.getComputedStyle(e).zIndex||0) => z>a ? z:a ,0);
let z = [...document.querySelectorAll`*`].reduce((a,e,i,t,z=+window.getComputedStyle(e).zIndex||0) => z>a ? z:a ,0);
console.log(z);
<div style="z-index: 100"></div>
<div style="z-index: 3000"></div>
<div style="z-index: 200"></div>
Very simple code using map and filter
function calMaxZIndex() {
return Array.from(document.querySelectorAll('body *'))
.map(a => parseFloat(window.getComputedStyle(a).zIndex || a.style.zIndex))
.filter(a => !isNaN(a))
.sort()
.pop()
}
function getMax() {
const max = calMaxZIndex() ?? 0
console.log({
max
})
}
getMax()
#ticket-box {
text-align: center;
position: fixed;
top: 0;
right: 0;
width: 100%;
background-color: #e9d295;
padding: 5px;
z-index: 6;
}
<div id="menu">
CLOSE
<ul style="text-align:center;list-style-type:none;">
<li>FILM</li>
<li>MUSIC</li>
<li>SPORTS</li>
<li>FINANCE</li>
</ul>
</div>
<div id="ticket-box">Have you bought your tickets for friday's event? No?! Grab yours now!</div>
<center>MENU</center>
Based on previous answers:
function with some modifications
let zIndexMax = () =>
[...document.querySelectorAll('body > *')]
.map(elem => parseInt(getComputedStyle(elem).zIndex, 10) || 0)
.reduce((prev, curr) => curr > prev ? curr : prev, 1);
Prototype
HTMLElement.prototype.zIndexMax = function () {
return [...this.children]
.map(elem => parseInt(getComputedStyle(elem).zIndex, 10) || 0)
.reduce((prev, curr) => curr > prev ? curr : prev, 1);
}
usage
document.querySelector('body').zIndexMax();
After looking through a lot of solutions here on StackOverflow - I've seen that none of them actually works correctly and considers how is zIndex actually working. I have written a solution that also takes into consideration the stacking context. You can refer to this article to understand how stacking context works in CSS.
const getZIndex = el => {
const computedStyle = getComputedStyle(el, null)
const zIndex = computedStyle.getPropertyValue('z-index')
return zIndex !== 'auto' ? parseInt(zIndex) : null
}
const getZIndexWithinStackingContext = (el, context) => {
let zIndex = getZIndex(el)
if (!zIndex) return null
let result = zIndex
while (el.parentElement !== context) {
el = el.parentElement
zIndex = getZIndex(el)
if (zIndex) {
result = zIndex
}
}
return result
}
const createZIndex = (overVisibleOnly = false, context = document.body) => {
const elements = [...document.querySelectorAll('body *')]
let highestZIndex = 0
elements.forEach(el => {
if (overVisibleOnly) {
const isVisible = !!el.offsetParent
if (!isVisible) return
}
const zIndex = getZIndexWithinStackingContext(el, context)
if (zIndex && zIndex > highestZIndex) {
highestZIndex = zIndex
}
})
return highestZIndex + 1
}
Note that this solution considers all elements, and not only positioned ones because they can become positioned after some class is added. But you can fix this easily by just adding a check for the position computed style property.
I have found the provided methods don't work when there have been z-indices changed dynamically on the page (the current methods only grab the originally set z-indices).
This function also works with dynamically added z indices:
function find_max_z_index() {
const all_z = [];
document.querySelectorAll("*").forEach(function(elem) {
all_z.push(elem.style.zIndex)
})
const max_index = Math.max.apply(null, all_z.map((x) => Number(x)));
return(max_index)
}
Here is my two-line function:
const getMaxZIndex = function () {
const elements = [...document.querySelectorAll("body *")];
return Math.max(...elements.map(x => parseInt(getComputedStyle(x, null).zIndex) || 0));
};
console.log(getMaxZIndex());

Categories

Resources