7
votes

I need to find a way to draw a 1000x1000 squares grid, each square is clickable and they must be independently color changeable. Like mines game. I can use HTML (pure or using Canvas or SVG), CSS and JavaScript for this.

I know how to create one grid with these characteristics with JavaScript and CSS, it does well with 10x10 squares, with 100x100 the squares will turn into tall rectangles and 1000x1000 it loads, but the "squares" are soo much compressed that borders meet each other and renders a full gray page.

I tried using HTML and JavaScript to draw SVG squares, the squares' size problem solves, but I don't know how to make they change color when clicked and when I set to load 1000x1000 squares it will freeze the browse and eventually crash the tab.

Is this feasible in any way?

EDIT

Sorry if I wasn't clear, but yes, I need scroll bars in that. They are no problem for me.

You can see the two trials I described here:

JavaScript and CSS

var lastClicked;
var grid = clickableGrid(100,100,function(el,row,col,i){
    console.log("You clicked on element:",el);
    console.log("You clicked on row:",row);
    console.log("You clicked on col:",col);
    console.log("You clicked on item #:",i);

    el.className='clicked';
    if (lastClicked) lastClicked.className='';
    lastClicked = el;
});

document.body.appendChild(grid);
     
function clickableGrid( rows, cols, callback ){
    var i=0;
    var grid = document.createElement('table');
    grid.className = 'grid';
    for (var r=0;r<rows;++r){
        var tr = grid.appendChild(document.createElement('tr'));
        for (var c=0;c<cols;++c){
            var cell = tr.appendChild(document.createElement('td'));
            ++i;
            cell.addEventListener('click',(function(el,r,c,i){
                return function(){
                    callback(el,r,c,i);
                }
            })(cell,r,c,i),false);
        }
    }
    return grid;
}
.grid { margin:1em auto; border-collapse:collapse }
.grid td {
    cursor:pointer;
    width:30px; height:30px;
    border:1px solid #ccc;
}
.grid td.clicked {
    background-color:gray;
}

JavaScript and HTML

 
    document.createSvg = function(tagName) {
        var svgNS = "http://www.w3.org/2000/svg";
        return this.createElementNS(svgNS, tagName);
    };
    
    var numberPerSide = 20;
    var size = 10;
    var pixelsPerSide = 400;
    
    
    
    var grid = function(numberPerSide, size, pixelsPerSide, colors) {
        var svg = document.createSvg("svg");
        svg.setAttribute("width", pixelsPerSide);
        svg.setAttribute("height", pixelsPerSide);
        svg.setAttribute("viewBox", [0, 0, numberPerSide * size, numberPerSide * size].join(" "));
        
        for(var i = 0; i < numberPerSide; i++) {
            for(var j = 0; j < numberPerSide; j++) {
              var color1 = colors[(i+j) % colors.length];
              var color2 = colors[(i+j+1) % colors.length];  
              var g = document.createSvg("g");
              g.setAttribute("transform", ["translate(", i*size, ",", j*size, ")"].join(""));
              var number = numberPerSide * i + j;
              var box = document.createSvg("rect");
              box.setAttribute("width", size);
              box.setAttribute("height", size);
              box.setAttribute("fill", color1);
              box.setAttribute("id", "b" + number); 
              g.appendChild(box);
              svg.appendChild(g);
            }  
        }
        svg.addEventListener(
            "click",
            function(e){
                var id = e.target.id;
                if(id)
                    alert(id.substring(1));
            },
            false);
        return svg;
    };
    
    var container = document.getElementById("container");
    container.appendChild(grid(100, 10, 2000, ["gray", "white"]));

    
<div id="container">
    
</div>

I will be trying implementing the given answers and ASAP I'll accept or update this question. Thanks.

SOLUTION

Just to record, I managed to do it using canvas to draw the grid and the clicked squares and added an event listener to know where the user clicks.

Here is the code in JavaScript and HTML:

function getSquare(canvas, evt) {
    var rect = canvas.getBoundingClientRect();
    return {
        x: 1 + (evt.clientX - rect.left) - (evt.clientX - rect.left)%10,
        y: 1 + (evt.clientY - rect.top) - (evt.clientY - rect.top)%10
    };
}

function drawGrid(context) {
    for (var x = 0.5; x < 10001; x += 10) {
      context.moveTo(x, 0);
      context.lineTo(x, 10000);
    }
    
    for (var y = 0.5; y < 10001; y += 10) {
      context.moveTo(0, y);
      context.lineTo(10000, y);
    }
    
    context.strokeStyle = "#ddd";
    context.stroke();
}

function fillSquare(context, x, y){
    context.fillStyle = "gray"
    context.fillRect(x,y,9,9);
}

var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');

drawGrid(context);

canvas.addEventListener('click', function(evt) {
    var mousePos = getSquare(canvas, evt);
    fillSquare(context, mousePos.x, mousePos.y)
}, false);
<body>
    <canvas id="myCanvas" width="10000" height="10000"></canvas>
</body>
3
Have you tried just turning off the borders? Can you post a screenshot of what you're talking about? How much space do you have available?Evan Knowles
Take a look at the edit I made @EvanKnowles, I think now it's clearer.Rodrigo Martins de Oliveira

3 Answers

10
votes

Generating such a large grid with HTML is bound to be problematic. Drawing the grid on a Canvas and using a mouse-picker technique to determine which cell was clicked would be much more efficient.

This would require 1 onclick and/or hover event instead of 1,000,000. It also requires much less HTML code.

1
votes

I wouldn't initialize all the squares right off, but instead as they are clicked -

(function() {
var divMain = document.getElementById('main'),
    divMainPosition = divMain.getBoundingClientRect(), 
    squareSize = 4, 
    square = function(coord) { 
        var x = coord.clientX - divMainPosition.x + document.body.scrollLeft + 
            document.documentElement.scrollLeft, 
            y = coord.clientY - divMainPosition.y + document.body.scrollTop + 
            document.documentElement.scrollTop; 
        return {
            x:Math.floor(x / squareSize), 
            y:Math.floor(y / squareSize)
        }
    }

divMain.addEventListener('click', function(evt) { 
    var sqr = document.createElement('div'), 
        coord = square(evt); 
    sqr.className = 'clickedSquare'; 
    sqr.style.width = squareSize + 'px'; 
    sqr.style.height = squareSize + 'px'; 
    sqr.style.left = (coord.x * squareSize) + 'px'; 
    sqr.style.top = (coord.y * squareSize) + 'px';
    sqr.addEventListener('click', function(evt) { 
        console.log(this); 
        this.parentNode.removeChild(this); 
        evt.stopPropagation(); 
    }); 
    this.appendChild(sqr); 
}); 
}()); 
#main { 
    width:4000px;
    height:4000px;
    background-color:#eeeeee;
    position:relative; 
}

.clickedSquare {
    background-color:#dd8888;
    position:absolute; 
}
<div id="main">
</div>
  • Uses CSS positioning to determine which square was clicked on,
  • doesn't initialize a square until it's needed.

Granted I imagine this would start to have a negative impact to use r experience, but that would ultimately depend on their browser and machine.

0
votes

Use the same format you noramlly use, but add this:

sqauareElement.height = 10 //height to use
squareElement.width = 10 //width to use

This will add quite a large scroll due to the size, but it's the only logical explanation I can come up with.