How to create a low memory consumption snap to grid canvas system HTML + JS? - javascript

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));

Related

How to get all sibling div's inside overlapping div area?

I have a large div and smaller siblings divs positioned inside it like this:
.large{
height:20rem;
width:20rem;
background-color:red;
position:absolute;
}
.item1{
height:5rem;
width:5rem;
background-color:blue;
top:1rem;
position:absolute;
}
.item2{
height:5rem;
width:5rem;
background-color:green;
top:3rem;
left:2rem;
position:absolute;
}
.item3{
height:5rem;
width:5rem;
background-color:yellow;
top:1rem;
left:6rem;
position:absolute;
}
<div class="large"></div>
<div class="item1"></div>
<div class="item2"></div>
<div class="item3"></div>
How do I get all the small divs within the large div dimensions?
Is there something similar to elementsFromPoint? Maybe something like elementsFromArea
Edit:
assume .large spans 320 pixels x 320 pixels
and I have multiple smaller divs on my screen, which can either be overlapping .large or outside it
How do I find divs which are overlapping .large?
Maybe we could get the position of .large & we already have the height and width of it and add it to some function like this:
elementsFromArea(large_x,large_y,large_height,large_width);
This should return an array of all the divs within that given range
(.large is merely for reference sake, I simply want to pass any given square area & find all the divs lying within it )
Bounty Edit:
The solution provided by #A Haworth works but I'm looking for a solution which doesn't involve having to loop and check every single element
this fiddle explains what I'm ultimately trying to achieve
Any clever work around will be accepted too!
You can use getBoundingClientRect to find the left, right, top and bottom bounds of each element.
Then test whether there is overlap with the large element by seeing whether the left is to the left of the right side of the large element and so on:
if ( ((l <= Right) && (r >= Left)) && ( (t <= Bottom) && (b >= Top)) )
To give a more thorough test, in this snippet the blue element has been pushed down so it only partially overlaps the large one and the yellow element doesn't overlap at all.
const large = document.querySelector('.large');
const largeRect = large.getBoundingClientRect();
const Left = largeRect.left;
const Right = largeRect.right;
const Top = largeRect.top;
const Bottom = largeRect.bottom;
const items = document.querySelectorAll('.large ~ *');
let overlappers = [];
items.forEach(item => {
const itemRect = item.getBoundingClientRect();
const l = itemRect.left;
const r = itemRect.right;
const t = itemRect.top;
const b = itemRect.bottom;
if (((l <= Right) && (r >= Left)) && ((t <= Bottom) && (b >= Top))) {
overlappers.push(item);
}
});
console.log('The items with these background colors overlap the large element:');
overlappers.forEach(item => {
console.log(window.getComputedStyle(item).backgroundColor);
});
.large {
height: 20rem;
width: 20rem;
background-color: red;
position: absolute;
}
.item1 {
height: 5rem;
width: 5rem;
background-color: blue;
top: 19rem;
position: absolute;
}
.item2 {
height: 5rem;
width: 5rem;
background-color: green;
top: 3rem;
left: 2rem;
position: absolute;
}
.item3 {
height: 5rem;
width: 5rem;
background-color: yellow;
top: 1rem;
left: 26rem;
position: absolute;
}
<div>
<div class="large"></div>
<div class="item1"></div>
<div class="item2"></div>
<div class="item3"></div>
</div>
Note, this snippet tests only those elements which are siblings of large in the CSS sense, that is that follow large. If you want all siblings whether they follow large or come before it then go back up to large's parent and get all its children (which will of course include large).
The IntersectionObserver API describes exactly what you are looking for. It's a relatively new API so I'm not surprised the other answers have not referenced it.
I have personally used it in a lazy loading context for displaying large tables without rendering 9001 rows at once. In my case, I would use the IntersectionObserver to determine when the last table row was in the user's field of view, and then I would load additional rows. It's very performant as it doesn't require any loops that poll the position of DOM elements, and the browser is free to optimize it however it likes.
Stealing from MDN, here's a simple way to create an IntersectionObserver. I've commented out options which I don't think you need.
let options = {
root: document.querySelector('.large'),
// rootMargin: '0px',
// threshold: 1.0
}
let observer = new IntersectionObserver(callback, options);
The callback is a function that fires whenever an element's intersection of .large changes by a certain threshold. If threshold = 0 (the default value and what I think you want in your case), then it will fire even if only 1 pixel overlaps.
Once you've created an IntersectionObserver with .large as the root, you will then want to .observe() the smaller divs so the IntersectionObserver can report on when they intersect .large.
Again, stealing from MDN, the format of the callback is as follows. Please note that the callback fires on intersection changes, meaning that if a smaller div that used to intersect .large no longer does, it will be in the list of entries. To get elements that are intersecting .large you will want to filter entries such that only those where entry.isInterecting === true are present. From the filtered list of entries you can then grab entry.target from every entry.
let callback = (entries, observer) => {
entries.forEach(entry => {
// Each entry describes an intersection change for one observed
// target element:
// entry.boundingClientRect
// entry.intersectionRatio
// entry.intersectionRect
// entry.isIntersecting
// entry.rootBounds
// entry.target
// entry.time
});
};
The solution provided by #A Haworth works but I'm looking for a solution which doesn't involve having to loop and check every single element
I don't know how to achieve this without a loop, if we are handle an array of elements, but you can test this solution with the resizeObserver and loops.
// Init elements
const items = [...document.querySelectorAll('.item')];
const frame = document.getElementById('frame');
const resultElement = document.getElementById('for-result');
// Creating an array of properties
// Math.trunc() removing any fractional digits
const itemsProperties = items.map(item => {
return {
width: item.getBoundingClientRect().width,
height: item.getBoundingClientRect().height,
x: Math.trunc(item.getBoundingClientRect().x),
y: Math.trunc(item.getBoundingClientRect().y),
};
});
function within_frame(frameSize) {
const inside = [];
for (const i in itemsProperties) {
// Determine current height and width of the square
// Because X, Y is TOP, LEFT, and we need RIGHT, BOTTOM values.
const positionY = itemsProperties[i].height + itemsProperties[i].y;
const positionX = itemsProperties[i].width + itemsProperties[i].x;
// If the position square less than or equal to the size of the inner frame,
// then we will add values to the array.
if (
positionY <= frameSize.blockSize &&
positionX <= frameSize.inlineSize
) {
inside.push(itemsProperties[i]);
}
}
//returns all the elements within the frame bounds
return inside;
}
// Initialize observer
const resizeObserver = new ResizeObserver(entries => {
// Determine height and width of the 'frame'
const frameSize = entries[0].borderBoxSize[0];
// Return an array of values inside 'frame'
const result = within_frame(frameSize);
//console.log(result);
// for result
resultElement.innerHTML = result.map(
(el, idx) => `<code>square${idx + 1} position: ${el.x}px x ${el.y}px</code>`
);
});
// Call an observer to watch the frame
resizeObserver.observe(frame);
#frame {
height: 10rem;
width: 10rem;
display: inline-block;
resize: both;
border: solid black 0.5rem;
overflow: auto;
position: absolute;
z-index: 1;
}
.item {
height: 2rem;
width: 2rem;
position: absolute;
}
/* for result */
pre {
position: fixed;
right: 0.5rem;
top: 0.5rem;
border: 2px solid black;
padding: 0.5rem 1rem;
display: flex;
flex-flow: column;
}
#for-result {
font-weight: bold;
font-size: 1.5em;
}
<div id="frame"></div>
<div class="item" style="background-color: red"></div>
<div class="item" style="background-color: green; top: 50%"></div>
<div class="item" style="background-color: blue; top: 20%; left: 30%"></div>
<div class="item" style="background-color: pink; top: 60%; left: 20%"></div>
<div class="item" style="background-color: yellow; top: 25%; left: 10%"></div>
<pre id="for-result"></pre>
Heads up: A frivolous and probably useless answer
However the question itself seems quite frivolous too. No real world use case has been provided yet and I can't think of any either. Similarly, in theory my answer could be useful, but you're more likely struck by an asteroid than finding yourself needing it.
The point of posting is more that it provides some perspective on the performance of the other proposed solution. You can see you need at least hundreds of elements before performance starts being a concern.
My "answer" only works if:
items are rectangles
items cannot overlap
The potential "performance problem"
Perhaps the "not a loop" requirement refers to having a solution that doesn't require you to loop through a potentially large amount of other items in JS? This could be a valid concern, if the number of items can ever get really large.
Say that the area you're testing is relatively small compared to the items, and there are thousands of items that may or may not be inside, looping all of them might be relatively costly. Especially if you need to give each an event listener.
As already pointed out, it would be nice if a native API similar to document.getElementFromPoint existed, as that would undoubtedly be more performant than implementing in JS.
However that API does not exist. Probably because nobody ever found themselves needing it in a real world use case.
Sampling points of the frame
Now you could just use the document.ElementFromPoint API on every single point of the frame. However that would scale even worse with the frame's size.
But do we need to check every point to guarantee we're detecting all elements? Not if the elements can't overlap: since the smallest element is likely still many pixels high and wide, we could create a grid of points with those minimum values. As long as the elements don't have changing dimensions (or they can only grow) we only need to loop them once (to determine the smallest), not on updates. Note I do loop them every time, to account for setting changes. If you're sure elements have fixed dimensions you only need it once at the start of your script.
Of course, you do now have to loop over points instead. However...
In the best case scenario, where the minimum element is equally wide and high (or bigger), you only need to check 4 points. In fact I used this in a function to generate random cubes, to avoid overlap with earlier cubes.
It doesn't work on overlapping elements as document.ElementFromPoint only knows about the topmost. You could work around that by temporarily setting a z-index, but I had to stop somewhere.
Does it perform better?
I'm not sure at all whether this would ever make sense to do, but I don't immediately see another way to handle large amounts of items.
In the best case of needing just 4 points (small area to check overlap), it's hard to imagine another approach being faster, if the other approach needs to go through thousands of elements in JS. Even with up to a few tens of points it'll probably still be "fast" regardless of how many elements on the page.
let allItems = [...document.querySelectorAll('.item')];
const frame = document.getElementById('frame')
function measureLoop() {
const start = performance.now();
const large = document.querySelector('#frame');
const largeRect = large.getBoundingClientRect();
const Left = largeRect.left;
const Right = largeRect.right;
const Top = largeRect.top;
const Bottom = largeRect.bottom;
const items = document.querySelectorAll('#frame ~ *');
let overlappers = [];
items.forEach(item => {
const itemRect = item.getBoundingClientRect();
const l = itemRect.left;
const r = itemRect.right;
const t = itemRect.top;
const b = itemRect.bottom;
if (((l <= Right) && (r >= Left)) && ((t <= Bottom) && (b >= Top))) {
overlappers.push(item);
}
});
document.getElementById('result-loop').innerHTML = overlappers.length;
document.getElementById('time-loop').innerHTML = performance.now() - start;
}
function randomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
function within_frame(frame, items) {
const rect = frame.getBoundingClientRect();
const frameX = rect.left;
const frameY = rect.top;
const frameWidth = frame.clientWidth;
const frameHeight = frame.clientHeight;
const smallestWidth = Math.min(...(items.map(i => i.clientWidth)));
const smallestHeight = Math.min(...(items.map(i => i.clientHeight)));
const set = new Set();
let points = 0;
const lastY = frameHeight + smallestHeight;
const lastX = frameWidth + smallestWidth;
for (let y = 0; y < lastY; y += smallestHeight) {
for (let x = 0; x < lastX; x += smallestWidth) {
points++;
const checkX = Math.min(frameX + x, rect.right)
const checkY = Math.min(frameY + y, rect.bottom)
// Note there is always a result, but sometimes it's not the elements we're looking for.
// Set takes care of only storing unique, so we can loop a small amount of elements at the end and filter.
set.add(document.elementFromPoint(checkX, checkY));
}
}
set.forEach(el => (el === frame || el === document.documentElement || !items.includes(el)) && set.delete(el))
document.getElementById('points').innerHTML = points;
return set;
}
function measure() {
// Frame needs to be on top for resizing, put it below while calculating.
frame.style.zIndex = 1;
const start = performance.now();
const result = within_frame(frame, allItems)
const duration = performance.now() - start
document.getElementById('result').innerHTML = [...result.entries()].length;
document.getElementById('time').innerHTML = duration;
// Restore.
frame.style.zIndex = 3;
}
document.getElementById('measure').addEventListener('click', () => {measure(); measureLoop();})
const overlapsExisting = (el) => {
return within_frame(el, allItems);
}
let failedGenerated = 0;
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function spawnCubes() {
frame.style.zIndex = 1;
allItems.forEach(item => item.parentNode.removeChild(item));
const nPoints = document.getElementById('nCubes').value;
const cubeSize = document.getElementById('size').value;
let newItems = [];
let failedGenerated = 0;
for (let i = 0; i < nPoints && failedGenerated < 1000; i++) {
// Sleep so that stuff is drawn.
if ((i + failedGenerated) % 100 === 0) {
document.getElementById('nCubes').value = newItems.length;
await sleep(0);
}
const el = document.createElement('div');
el.className = 'item';
//el.innerHTML = i;
el.style.backgroundColor = randomColor();
el.style.top = `${Math.round(Math.random() * 90)}%`;
el.style.left = `${Math.round(Math.random() * 60)}%`;
el.style.width = `${cubeSize}px`;
el.style.height = `${cubeSize}px`;
frame.after(el);
const existingOverlapping = within_frame(el, newItems);
if (existingOverlapping.size > 0) {
i--;
failedGenerated++;
el.parentNode.removeChild(el);
continue;
}
newItems.push(el);
}
console.log('failedAttempts', failedGenerated);
allItems = newItems;
frame.style.zIndex = 3;
document.getElementById('nCubes').value = newItems.length;
}
frame.addEventListener('mouseup', () => {measure(); measureLoop()});
spawnCubes().then(() => {measure(); measureLoop();});
document.getElementById('randomize').addEventListener('click', e => {
spawnCubes().then(measure);
})
#frame {
height: 3rem;
width: 3rem;
display: inline-block;
resize: both;
border: solid black 0.1rem;
overflow: auto;
position: absolute;
z-index: 3;
}
.item {
height: 1rem;
width: 1rem;
position: absolute;
z-index: 2;
}
.controls {
position: fixed;
bottom: 4px;
right: 4px;
text-align: right;
}
<div id="frame"></div>
<div class="controls">
<button id="measure">
measure
</button>
<button id="randomize">
spawn cubes
</button>
<div>
N cubes:
<input id="nCubes" type="number" value="40">
</div>
<div>
Cube size:
<input id="size" type="number" value="16">
</div>
<div>
N inside large:
<span id="result">
</span>
</div>
<div>
Time (ms):
<span id="time">
</span>
</div>
<div>
Points:
<span id="points">
</span>
</div>
<div>
N inside large (loop):
<span id="result-loop">
</span>
</div>
<div>
Time (ms) (loop):
<span id="time-loop">
</span>
</div>
</div>

Remove (not hide) overflow of many divs from container, or constrain amount to container?

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>

Leap.JS: How can I select items on a webpage using the LeapMotion?

I am looking to select items in a web page using the LeapMotion and I am struggling with programming this interaction.
Using this code as a base (but updating the link so it connects to the current SDK) I can get my cursor to move around the window based on where my hand is in space. However, I do not know how to make the equivalent of the event listener "click" using the LeapMotion. I am using Leap.js and have built a crude GUI using svg.js.
How do I program an event listener that selects using the LeapMotion in Javascript?
I have working code with Leap motion. I did quize software. Here cursor is a div element which styled as:
#cursor {
width: 60px;
height: 60px;
position: fixed;
margin-left: 20px;
margin-top: 20px;
z-index: 99999;
opacity: 0.9;
background: black;
border-radius: 100%;
background: -webkit-radial-gradient(100px 100px, circle, #f00, #ff6a00);
background: -moz-radial-gradient(100px 100px, circle, #f00, #ff6a00);
background: radial-gradient(100px 100px, circle, #f00, #ff6a00);
}
and Java script at bottom. What I did, I get HTML element by position and triggering click event on it by tap gestures.
var w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
// Setting cursor and output position
window.cursor = $('#cursor');
var controller= Leap.loop(function(frame) {
if (frame.pointables.length > 0) {
try
{
var position = frame.pointables[0].stabilizedTipPosition;
var normalized = frame.interactionBox.normalizePoint(position);
var cx = w * normalized[0];
var cy = h * (1 - normalized[1]);
$('#cursor').css('left', cx);
$('#cursor').css('top', cy);
}catch(e){
console.error(e);
}
}
});
controller.use('screenPosition', {
scale: 1
});
controller.on('gesture', onGesture);
function onGesture(gesture,frame)
{
try
{
// If gesture type is keyTap
switch(gesture.type)
{
case 'keyTap':
case 'screenTap':
var position = frame.pointables[0].stabilizedTipPosition;
var normalized = frame.interactionBox.normalizePoint(position);
//Hiding cursor for getting background element
cursor.hide();
// Trying find element by position
var cx = w * normalized[0];
var cy = h * (1 - normalized[1]);
var el = document.elementFromPoint(cx, cy);
cursor.show();
console.log(el);
if (el) {
$(el).trigger("click");
}
break;
}
}
catch (e) {
console.info(e);
}
}

Create custom square grid using jquery

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>

How to compare positions of two elements in javascript

I have been trying to make this work since the beginning of this week but couldn't figure it out. I have two elements in my HTML file. They are on the same vertical lane, element1 is moving 60ems to the left horizontally with each button click. I have to compare the positions of these two elements and detect whether element1 has passed the element2 with an if/else statement. When it passes I want to move the element1 to its initial position and start over. But if statement never shoots, it always jumps to else statement.
I have tried comparing following properties:
$('#elemet1').position();
$('#elemet1').position().left;
$('#elemet1').offset();
$('#elemet1').offsetLeft;
$('#elemet1').css("position");
$('#elemet1').css("left");
$('#elemet1').css("margin");
$('#elemet1').left;
I tried to compare the positions like below:
$(function () {
$("button").click(function () {
var position1 = $('#element1').css("left");
var position2 = $('#element2').css("left");
if (position1 <= position2) {
$("#element1").animate({left:'+=240em'}, 600);
}
else {
$("#element1").animate({left:'-=60em'}, 600);
}
});
});
If you can give me an answer or direct me to another post I will appreciate it. Thank you in advance.
EDIT: The style I use is below:
.element2 {
position: relative;
width: 60em;
height: 40em;
bottom: 8em;
background: rgba(242, 210, 139, 0.6);
border-radius: 1.2em;
z-index: 1;
overflow: hidden;
#element1 {
position: absolute;
margin-left: 180em;
width: 60em;
height: 40em;
z-index: 1;
}
And I should have mentioned this at the beginning: element1 is inside element2:
<div class="element2">
<img id="element1" src="image/bg1.png" />
</div>
This will give you the current position of an element:
$('#element').position().left;
If you need position relative to the document (instead of relative to a parent element) you can use:
$('#element').offset().left;
You should just be able to compare the return values of these for the different elements directly. Here's some sample code that works for me, note there is no need to parse since they always return pixel values.
var pos = Math.max($(w).scrollTop(), 38),
top = $(ballon).position().top,
dist = Math.abs(pos-top),
time = dist > 1000 ? 2000 : dist / 0.15;
if (pos + $(w).height() >= pHeight) {
pos = pHeight - bHeight - gHeight;
$(ballon).animate({'top': pos}, time, 'easeInOutSine');
}
else if (dist > 150 || pos < 100) {
$(ballon).animate({'top': pos}, time, 'easeInOutBack');
}

Categories