I am working on a project trying to make something resembling an etch-a-sketch. I have a 780x780px square, and I am trying to get a 16x16 grid, using a series of smaller square divs.
It is on this grid that I have the hover effect. I keep getting a 15x17 grid of square divs because the last square of the row won't fit. I have margins set to 1px and padding set to 0 so I figured that to fit 16 squares on a 780px wide row, it would require me to take into account the margins (15 1px margins) and from there I could divide (780-15) by 16, the number of squares I want.
That isn't working, and the next step of this project is to have a button where the user could input any number of squares for the row/column and have either a larger or smaller squared grid STILL ON the 780x780 square. Does anyone have any ideas? I'm pretty stumped.
$(document).ready(function() {
var original = 16;
for (var y = 0; y < original * original; y++) {
$(".squares").width((780 - 15) / original);
$(".squares").height((780 - 17) / original);
$("<div class='squares'></div>").appendTo('#main');
}
$('.squares').hover(
function() {
$(this).addClass('hover');
}
)
});
function gridq() {
$('.squares').removeClass('hover');
$('div').remove('.squares');
var newgrid = prompt("How many squares on each side?");
var widthscreen = 192;
if (newgrid > 0) {
for (var x = 0; x < newgrid * newgrid; x++) {
$(".squares").width(widthscreen / newgrid);
$(".squares").height(widthscreen / newgrid);
$("<div class='squares'></div>").appendTo('#main');
}
$('.squares').hover(
function() {
$(this).addClass('hover');
}
)
}
}
#main {
height: 780px;
width: 780px;
background-color: antiquewhite;
position: relative;
}
.squares {
margin: 1px;
padding: 0;
background-color: aquamarine;
display: inline-block;
float: left;
}
.hover {
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<div id=main>
</div>
<button onclick="gridq()">Go Again!</button>
Try this snippet? Grid initialisation is set in the grid() function and then called later when necessary. The width is set dynamically to the 16th square's right side.and the remaining squares fill out as necessary.
var wide = (780 - 15) / 16,
tall = (780 - 17) / 16; // set the square dimensions. this can be incorporated into the grid() function with 16 replaced by 'original'
function grid(x, y) {
var original = x,
y = y;
$("#main").empty(); // empty and restart
$("#main").width(wide * (original + 1));
for (var i = 0; i < original * y; i++) {
$("<div class='squares'></div>").appendTo('#main');
}
var square = $(".squares");
square.width(wide);
square.height(tall);
var side = square.eq(original - 1).position().left + square.width() + 2; // tighten the #main width
$("#main").width(side);
$('.squares').hover(
function() {
$(this).addClass('hover');
}
)
}
grid(16, 16); // starting dimension
function gridq() {
$('.squares').removeClass('hover');
$('div').remove('.squares');
var newgrid = prompt("How many squares on each side?");
var widthscreen = 192;
if (newgrid > 0) {
grid(newgrid, newgrid);
}
}
#main {
background-color: antiquewhite;
position: relative;
}
.squares {
margin: 1px;
padding: 0;
background-color: aquamarine;
display: inline-block;
float: left;
}
.hover {
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<div id='main'>
</div>
<button onclick="gridq()">Go Again!</button>
just got beat to it...ill post this as it answers the question slightly differently and I feel is a little cleaner. Also I added in a width and a height prompt.
see the codepen here
As a side note...its good practice to make the names of your variables make sense. Also I find that breaking down your code problems into smaller more manageable chunks makes it seem less overwhelming...one step at a time :)
Enjoy and good luck!
$(document).ready(function() {
//declare the variables at the top of your functions...it enables us to change them later
var columnWidthCount = 16;
var columnHeightCount = 16;
function makeBoxes() {
//boxcount lets us set how many times we want the for loop to run...when we change the columns/rows later this variable will be updated
var boxCount = columnWidthCount * columnHeightCount;
//
for (var i = 0; i < boxCount; i++) { //loop through each box
//any code you place in here will execute each time we loop around
$("<div class='squares'></div>").appendTo('#main');
}
//we only want to declare this once so we place it after the loop
$(".squares").width((780 / columnWidthCount) - 2);
$(".squares").height((780 / columnHeightCount) - 2);
$('.squares').hover(
function() {
$(this).addClass('hover');
}
);
}
//fire the initial function
makeBoxes();
// fire function after click
$('button').on("click", function() {
$('div').remove('.squares');
var squaresHigh = prompt("How many squares high? (must be a number)");
var squaresWide = prompt("How many squares wide? (must be a number)");
//prompt returns a string...use parseInt to turn that number string into an integer
columnWidthCount = parseInt(squaresWide);
columnHeightCount = parseInt(squaresHigh);
makeBoxes();
});
});
#main {
height: 780px;
width: 780px;
background-color: antiquewhite;
position: relative;
font-size:0;
white-space:nowrap;
}
.squares {
margin: 1px;
padding: 0;
background-color: aquamarine;
display: inline-block;
float: left;
}
.hover {
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<div id=main>
</div>
<button>Go Again!</button>
Related
So I'm building this app which is an implementation of the game "Mancala". In a position of the board there can be "seeds" (game's piece) which I chose to represent as images.
In the initial setup of the game, there are N seeds in each position of the board. I represent this as N equal images ("seed.png") printed randomly in the respective position.
I want images to overlap, so even when N is a big number, they will all fit in the position ("see image nrº1"). What I accomplished so far is a random distribution with little to none overlapping and some "seeds" are getting out of the circle.
This is the code I have, built in JavaScript:
function init_board() {
const board = document.getElementById("board");
for(let i = 0; i < 2; i++) {
const tr = board.insertRow();
if(i == 0) {
tr.insertCell().setAttribute("rowSpan", "2");
}
for(let j = 0; j < 6; j++) {
var x = tr.insertCell();
for(let k = 0; k < 20; k++) {
var img = document.createElement("img");
img.src = "images/seed.png";
img.height = "10";
img.width = "10";
img.style.position = "relative";
img.style.left = Math.floor(Math.random() * 7) + "px";
img.style.top = -7 + Math.floor(Math.random() * 14) + "px";
x.appendChild(img);
}
}
if(i == 0) {
tr.insertCell().setAttribute("rowSpan", "2");
}
}
With the following formatting:
#board {
margin-left: auto;
margin-right: auto;
position: relative;
top: 30%;
}
td {
width: 75px;
height: 75px;
border: 3px solid darkred;
border-radius: 40px;
background: antiquewhite;
}
table {
border: 5px solid darkred;
border-radius: 20px;
background: burlywood;
}
Image Nrº1 N=20: https://imgur.com/a/7aNVsUb,
Image Nrº2 where N=30 and the seeds change the size of the circle: https://imgur.com/a/2iHXwyd
Thank you in advance!
To apply width and height correctly to td tag, you need to make it an inline-block element.
td:not([rowspan="2"]) {
display: inline-block;
}
Your pen updated (with seeds=30): CodePen
I want to make a background that adds and removes square divs based on the size of the container, which is affected by resizing the window.
To add the divs, I followed the examples on this post here. But, resizing the screen continues to multiply the number of divs. Is there a way to constrain the amount of squares to the size of the container, or to remove the overflow?
(I don't want to simply css overflow:hidden because that doesn't solve the problem of a billion divs being multiplied.) And I'm an absolute javascript newbie, so bear with me!
let contain = document.getElementById("squareContain");
let width = contain.offsetWidth;
let height = contain.offsetHeight;
var containerArea = width * height;
var canAdd = Math.floor(containerArea/1600); //For 40px x 40px squares
function multiplyNode(node, count, deep) {
for (var i = 0, copy; i < count - 1; i++) {
copy = node.cloneNode(deep);
node.parentNode.insertBefore(copy, node);
}
}
$(window).on("resize", function(){
multiplyNode(document.querySelector('.square'), canAdd, false);
}).resize();
Edit jsfiddle
Currently you only calculate how many squares fit once, but you need recalculate each time window size changed:
let contain = document.getElementById("squareContain");
function canAdd()
{
let square = contain.children[0],
cWidth = contain.offsetWidth,
cHeight = contain.offsetHeight,
sWidth = square.offsetWidth,
sHeight = square.offsetHeight;
return Math.floor(cWidth / sWidth) * Math.floor(cHeight / sHeight);
}
function multiplyNode(node, count, deep) {
if (contain.children.length == count)
return;
if (contain.children.length < count)
{
for (var i = 0, copy; i < count - 1; i++) {
copy = node.cloneNode(deep);
node.parentNode.insertBefore(copy, node);
}
}
else
{
while(contain.children.length > count)
{
contain.removeChild(contain.children[contain.children.length -1]);
}
}
}
$(window).on("resize", function(){
multiplyNode(contain.querySelector('.square'), canAdd(), false);
}).resize();
.square_container{
background-color: #ccc;
position: fixed;
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-content: start;
flex-wrap: wrap;
margin: 0 auto;
padding: 0 auto;
}
.square{
width: 40px;
height: 40px;
background-color: blue;
border: 1px solid red;
margin: 0;
padding: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="squareContain" class="square_container">
<div class="square"></div><!--...etc...-->
</div>
I have created a grid with div, class and id. I want to randomly create a yellow square and I want to assign an id= 'yellowSquare' how do I do it?
var grid = document.getElementById("grid-box");
for (var i = 1; i <= 100; i++) {
var square = document.createElement("div");
square.className = 'square';
square.id = 'square' + i;
grid.appendChild(square);
}
var playerOne = [];
while (playerOne.length < 1) {
var randomIndex = parseInt(99 * Math.random());
if (playerOne.indexOf(randomIndex) === -1) {
playerOne.push(randomIndex);
var drawPone = document.getElementById('square' + randomIndex);
drawPone.style.backgroundColor = 'yellow';
}
}
#grid-box {
width: 400px;
height: 400px;
margin: 0 auto;
font-size: 0;
position: relative;
}
#grid-box>div.square {
font-size: 1rem;
vertical-align: top;
display: inline-block;
width: 10%;
height: 10%;
box-sizing: border-box;
border: 1px solid #000;
}
<div id="grid-box"></div>
I am new to Javascript / jQuery. Any help will be much appreciated ! Thank you
There are two options to your question. You can either change the id of the yellow square which is already created from your code, or create a child element within the square, which looks the same as your current solution. Creating a new child element will let you keep the numeric id pattern for the grid:
Changing the ID :
var element = document.getElementById('square' + randomIndex)
element.id = "yellowSquare";
Adding new element inside:
var node = document.createElement("DIV");
node.id = "yellowSquare";
node.style = "background-color:yellow;height:100%;width:100%;";
var element = document.getElementById('square' + randomIndex)
element.appendChild(node);
I set the styling of the div child to 100% width and height, as it has no content, and would get 0 values if nothing was specified. This should make it fill the parent container.
There are also multiple other ways to achieve the same result, for instance with JQuery.
Use the HTMLElement method setAttribute (source);
...
var drawPone = document.getElementById('square' + randomIndex);
drawPone.style.backgroundColor = 'yellow';
drawPone.setAttribute('id', 'yellowSquare');
...
As you requested in your comment how to move the square i made an example how you can move it left and right using jQuery next() and prev() functions. However because your html elements are 1 dimensional it's not easy to move them up/down and check the sides for collisions. Better would be to create your html table like with rows and columns and this way create a 2 dimensional play field.
Also added a yellowSquery class for selection with $drawPone.addClass('yellowSquare');.
Also since you like to use jQuery I changed your existing code to jQuery function. Might help you learn the framework.
var $grid = $("#grid-box");
for (var i = 1; i <= 100; i++) {
var $square = $("<div>");
$square.addClass('square');
$square.attr('id','square' + i);
$grid.append($square);
}
var playerOne = [];
while (playerOne.length < 1) {
var randomIndex = parseInt(99 * Math.random());
if (playerOne.indexOf(randomIndex) === -1) {
playerOne.push(randomIndex);
var $drawPone = $('#square' + randomIndex);
$drawPone.addClass('yellowSquare');
}
}
$('#button_right').on('click', function(){
$yellowSquare = $('.yellowSquare')
$yellowSquareNext = $yellowSquare.next();
$yellowSquare.removeClass('yellowSquare');
$yellowSquareNext.addClass('yellowSquare');
});
$('#button_left').on('click', function(){
$yellowSquare = $('.yellowSquare')
$yellowSquarePrev = $yellowSquare.prev();
$yellowSquare.removeClass('yellowSquare');
$yellowSquarePrev.addClass('yellowSquare');
});
#grid-box {
width: 400px;
height: 400px;
margin: 0 auto;
font-size: 0;
position: relative;
}
#grid-box>div.square {
font-size: 1rem;
vertical-align: top;
display: inline-block;
width: 10%;
height: 10%;
box-sizing: border-box;
border: 1px solid #000;
}
.yellowSquare {
background-color: yellow;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="grid-box"></div>
<button id="button_left">left</button>
<button id="button_right">right</button><br>
What I'm trying to do (which I've been able to accomplish, but with poor performances) is to apply a sort of grid over the canvas, in order to be able to take inputs from the user about the origin point position. Once the input is received, the "draw" coordinates are provided via keyboard.
What I've managed to do in these days, was to calculate the width and height of the canvas, then divide it by the area of a standard 20x20 square (speaking in px). In this way I can loop on the result and create n squares, that I will render in display flex inside the grid element. Then this grid element is applied "over" the canvas.
Everything works, but there's a lot of divs going around, and if the user choses to shrink the div to let's say 10x10, then, that would have a great impact over the performances... So I'm trying to find out a lighter way to do this...
I've thought about using HR elements inside two divs that would be applied over the canvas. One div displays elements in column, and another in row. In this way I should obtain the grid, but what about the snap? How could I detect the intersection on the two HR elements and use that exact spot as position?
The reason of why I cannot directly draw the grid on the canvas is because this should remain as 'pure' as possible. Containing only the final draw of the user.
Here's the 'non optimized' code:
I'm using Angular 5 as framework.
<div class="draw-zone" #drawZone>
<div class="grid" #grid [ngClass]="{'activated': activateDrawZones}">
<div *ngFor="let block of gridBlocks" class="grid-block" [ngClass]="{'show': showGrid, 'ten-x-ten': blockSize === 10, 'twe-x-twe': blockSize === 20, 'thr-x-thr': blockSize === 30, 'fou-x-fou': blockSize === 40}"
#gridBlock (click)="draw($event, gridBlock)"></div>
</div>
<canvas #canvas [height]="canvasSize.y" [width]="canvasSize.x"></canvas>
</div>
The scss:
.draw-zone{
flex-grow: 2;
height: 100%;
position: relative;
canvas{
z-index: 10;
}
.grid{
top: 0;
left: 0;
z-index: 11;
width: 100%;
display: flex;
flex-wrap: wrap;
overflow: hidden;
position: absolute;
margin-left: -.1rem;
border-radius: .5rem;
align-content: stretch;
border: 1px solid transparent;
&.activated{
border-color: #3f51b5;
}
.grid-block{
opacity: 0;
border-right: 1px solid #3f51b5;
border-bottom: 1px solid #3f51b5;
&.show{
opacity: .1;
}
&:hover{
opacity: 1;
border-radius: 50%;
background-color: #3f51b5;
transform: scale(1.2);
}
&.ten-x-ten{
width: 10px;
height: 10px;
}
&.twe-x-twe{
width: 20px;
height: 20px;
}
&.thr-x-thr{
width: 30px;
height: 30px;
}
&.fou-x-fou{
width: 40px;
height: 40px;
}
}
}
And the component method to cal:
private calculateGrid() {
this.activateDrawZones = false;
this.canvasSize.x = this._drawZone.nativeElement.clientWidth;
this.canvasSize.y = this._drawZone.nativeElement.clientHeight;
const blocksCount = (this.canvasSize.x * this.canvasSize.y) / (this.blockSize * this.blockSize);
this.gridBlocks = [];
for (let i = 0; i < blocksCount; i++) {
this.gridBlocks.push({ size: this.blockSize });
}
this.activateDrawZones = true;
}
And the method that actually draws:
public draw(e: MouseEvent, block: HTMLDivElement, returnOnFail?: boolean) {
const x = block.offsetLeft + (this.blockSize / 2);
const y = block.offsetTop + (this.blockSize / 2);
if (this.firstClick) {
this.ctx.beginPath();
this.ctx.moveTo(x, y);
this.setCrosshair(x, y);
this.firstClick = false;
this.addPathToDrawSequence(x, y);
return;
}
if (this.isNotOnTheSameAxisAsTheLastInsert(x, y)) {
if (returnOnFail) { return; }
this.toggleDrawDirection();
this.draw(e, block, true);
return;
}
this.ctx.lineTo(x, y);
this.ctx.stroke();
this.setCrosshair(x, y);
this.addPathToDrawSequence(x, y);
}
As you can see, I'm applying the '.grid' element over the canvas element. The grid element contains all the blocks that are displayed in flex mode. As you can see the grid container has a display:flex and flex-wrap: wrap properties. In this way, when the user clicks over a block, I can guess the x, y coordinates by getting its position, relative to the parent. Which has the same dimensions as the canvas. Once that I have the x,y coords, i can draw on the canvas.
Yes, creating a multiplicity of DOM elements and trying to dynamically position and size them with javascript will not be particularly performant. I don't think hr elements will solve this problem for you.
First, have you considered drawing your grid directly onto the canvas?
Another option is to have a background image with the grid on it layered behind the canvas. This will automatically resize just as performantly as any other aspect of your webpage.
Now for the 'snapping' part. It looks like you've already figured out how to draw what you need on the canvas once you get the grid information you're looking for. What you need is a method to get which grid a user clicked on. I'm guessing that is why you overlaid all those divs...
Instead, canvas natively tracks mouse clicks. Using some techniques laid out here should be able to get you the grid interaction information you're looking for.
Edit: A method to generate and find grids:
var height = 100;
var width = 200;
var horizontal_grids = 8;
var vertical_grids = 4;
function bounding_grid_1d(length, grids, x) {
var divisions = [];
var grid_width = length / grids;
for ( i = 0; i <= grids; i++ ) {
if (x || x == 0) {
if (i*grid_width > x) {
divisions.push((i-1)*grid_width);
divisions.push(i*grid_width);
break;
}
else if (i*grid_width == x) {
divisions.push(i*grid_width);
break;
}
}
else {
divisions.push(i*grid_width);
}
}
return divisions;
}
console.log("Get all the x and y grid line locations");
console.log(bounding_grid_1d(width, horizontal_grids));
console.log(bounding_grid_1d(height, vertical_grids));
console.log("Get the x and y grid line locations that surround the coordinates (60,30)");
console.log(bounding_grid_1d(width, horizontal_grids, 60));
console.log(bounding_grid_1d(height, vertical_grids, 30));
I am writing an HTML5 board game and I am having issues with jQuery's offset(). The grid of DIVs that make up the game board reside within a wrapper DIV that has CSS that sets overflow:scroll/width and height:100%. The game board itself is quite larger, so the scroll can be quite a bit horizontally and vertically.
The problem is that when I click on a DIV and try to move the player to that board piece, the player shifts around and is never in a consistent place relative to the board piece clicked.
Code:
$(".boardGridPiece").click(function(){
if(!$(this).hasClass("room") && $(this).hasClass("eligibleMove")){
playerStartX = $(this).offset().left;
playerStartY = ($(this).offset().top;
player.css("left", playerStartX);
player.css("top", playerStartY);
determineEligibleMoves($(this).attr("id"));
}
});
You can see that when a board piece is clicked, the offset of the board piece is grabbed and set to the player's X and Y.
CSS:
#boardWrapper {
position:relative;
width:100%;
height:80%;
overflow: scroll;
}
#theGame {
background-color: #fff;
height: 1080px;
width: 1920px;
}
Depending on where the player is relative to the current scroll view, when I click on a board piece he shifts around in a very inconsistent manner. Sometimes he's far left of where I click, or far up, etc.
What am I doing wrong? How do I take into account relative scroll position to get consistent positioning?
Here's a board for you to play with as an example. Also, my stab at the jquery is included as well. Basically, it finds where you clicked, calculated the px distance in float form, and animates the soldier to slide to his new position:
var black = '<td style="background-color:black"><div class="boardGridPiece"></div></td>';
var white = '<td style="background-color:white"><div class="boardGridPiece"></div></td>';
var squares = [black, white];
var grid = "";
for (var i = 0; i < 9; i++) {
grid += "<tr>";
for (var j = 0; j < 16; j++) {
grid += squares[(i + j) % 2];
}
grid += "<\tr>";
}
$('#gameboard').append(grid);
var gridSelected = $('#gameboard').find('tr:nth-child(2)').find('td:first').find('div');
gridSelected.toggleClass('position');
$('.boardGridPiece').click(function () {
$('.position').removeClass('position');
var gridSelected = $(this);
gridSelected.toggleClass('position');
var thisBox = $('.position');
var finalX = 0;
var finalY = 0;
for (var i = 0; i < 9.00; i++) {
for (var j = 0; j < 16.00; j++) {
var aBox = $('#gameboard').find('tr:nth-child(' + (i + 1) + ')').find('td:nth-child(' + (j + 1) + ')').find('div');
if (thisBox.get(0) == aBox.get(0)) {
finalX = j + 1;
finalY = i;
i = j = 16; // soft break
}
}
}
var overX = (finalX * parseFloat(1920))/16.00;
var downY = (finalY * parseFloat(1080))/9.00;
$('#player').animate({ left: overX, top: downY });});
html {
background-color:gray;
}
#gameboard {
margin: 100px;
height:1080px;
width:1920px;
border:1px solid black;
}
.boardGridPiece {
height: 110px;
width: 110px;
}
.position {
box-sizing:border-box;
-moz-box-sizing:border-box;
-webkit-box-sizing:border-box;
border: 1px solid red;
}
#player {
position: absolute;
top: 120px;
left: 125px;
height: 200px;
width: auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="gameboard" style=""></table>
<img id="player" src="http://img2.wikia.nocookie.net/__cb20110819034426/halo/images/7/74/ODST_Helljumper.png" />