javascripthtmlcsssliding-tile-puzzle

How can I place divs absolutely in a 3x3 grid with JS


I'm trying to create a 3x3 puzzle, grid of blocks of divs and each time the page refreshes, js creates and array of random order of numbers from 1-9 and uses them to create the 'blocks', gives them the corresponding id's and appends them to the container But i'm having issues, as not all the blocks are displayed. Some blocks (precisely 2 blocks) are still in the default 'absolute' position (0, 0) while the rest are okay.

Please can anyone help find out what's wrong ?

#randomOrder is and an array of 1-9 randomly ordered

# CSS Snippet :

.container{
    width: 300px;
    padding: 3px;
    display: flex;
    aspect-ratio: 1;
    flex-wrap: wrap;
    position: relative;
    margin: 100px auto;
    background: #777;
    border-radius: 10px;
    box-shadow: 0 0 0 5px #000;
}

.block{
    width: 31%;
    height: 31%;
    margin: 1.5px;
    display: flex;
    font-size: 3rem;
    cursor: pointer;
    user-select: none;
    background: #eee;
    position: absolute;
    border-radius: 10px;
    align-items: center;
    justify-content: center;
    border: 2px solid #000;    
    box-shadow: 0 0 20px #555 inset;
}

# JS Snippet:

let r = 1, c = 1
let x = 4, y = 4
randomOrder.forEach(el => {
    block = document.createElement('div')
    block.classList.add('block')
    block.id = "b" + el
    if(r <= 3){
        if(c <= 3){
            block.style.left = x + 'px'
            block.style.top = y + 'px'
            c++
            x += 98
        }
        else{
            c = 1
            x = 4
            y += 98
            r++
        }
    }

    if (el == 9) {
        block.innerText = ""
    }
    else {
        block.innerText = el
    }
    container.appendChild(block)
})

This is what I'm getting below :

enter image description here

But This Is What I'm Expecting : (But in any random order)

enter image description here


Solution

  • There are these issues in your logic:

    1. The inner if condition should be if (c < 3) instead of if (c <= 3), since c still gets incremented in that if block

    2. The assignment to style.left and style.top should also happen when execution gets into the else block, so those two assignments should be made before the if

    3. block should be declared with a var or let or const keyword. Now it is implicitly defined as a global variable which is not good practice, and not allowed when running in strict mode.

    Not an issue, but the if (r <= 3) really is not necessary -- this will always be the case.

    With those remarks taken into account, the relevant code becomes:

        const block = document.createElement('div')
        block.classList.add('block')
        block.id = "b" + el
        block.style.left = x + 'px'
        block.style.top = y + 'px'
        if(c < 3){
            c++
            x += 98
        }
        else{
            c = 1
            x = 4
            y += 98
            r++
        }
    

    There is however an easier way to determine x and y, using the remainder operator (%) and the index you can get as argument in the forEach callback.

    Unrelated, but the final if..else can be shortened with the conditional operator (? :):

    const container = document.querySelector(".container");
    const randomOrder = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    
    randomOrder.forEach((el, i) => {
        const block = document.createElement('div');
        block.classList.add('block');
        block.id = "b" + el;
        block.style.left = (4 + 98*(i % 3)) + 'px'
        block.style.top = (4 + 98*Math.floor(i / 3)) + 'px'
        block.innerText = el == 9 ? "" : el;
        container.appendChild(block);
    });
    .container{
        width: 300px;
        padding: 3px;
        display: flex;
        aspect-ratio: 1;
        flex-wrap: wrap;
        position: relative;
        margin: 100px auto;
        background: #777;
        border-radius: 10px;
        box-shadow: 0 0 0 5px #000;
    }
    
    .block{
        width: 31%;
        height: 31%;
        margin: 1.5px;
        display: flex;
        font-size: 3rem;
        cursor: pointer;
        user-select: none;
        background: #eee;
        position: absolute;
        border-radius: 10px;
        align-items: center;
        justify-content: center;
        border: 2px solid #000;    
        box-shadow: 0 0 20px #555 inset;
    }
    <div class="container"></div>

    Here is what the % and Math.floor expressions evaluate to for all the possible values of the index i:

    index i i % 3 Math.floor(i / 3)
    0 0 0
    1 1 0
    2 2 0
    3 0 1
    4 1 1
    5 2 1
    6 0 2
    7 1 2
    8 2 2

    So you can see that i % 3 represents a column index and Math.floor(i / 3) a row index.