The user forwards an image and receives a response through a json on that image, one of the answers is as follows:
"faceRectangle": {
"top": 187,
"left": 458,
"width": 186,
"height": 186
},
The big question is:
Through the information shown above, how do I insert a square in each
top and left with the javascript?
MY CODE [CSS]
.teste {
border-radius: 0px 0px 0px 0px;
-moz-border-radius: 0px 0px 0px 0px;
-webkit-border-radius: 0px 0px 0px 0px;
border: 5px solid #efff0d;
}
MY CODE [HTML]
<div class="col s12 m12 l6 xl6 center-align">
<div class="container-image-uploaded">
<div id="teste"></div>
<img id="sourceImagem" class="responsive-img sourceImagem">
</div>
</div>
MY CODE [JAVASCRIPT]
document.getElementById('teste').innerHTML.style.width += obj[o].faceRectangle.width + "px";
document.getElementById('teste').innerHTML.style.height += obj[o].faceRectangle.height + "px";
document.getElementById('teste').innerHTML.style.top += obj[o].faceRectangle.top + "px";
document.getElementById('teste').innerHTML.style.left += obj[o].faceRectangle.left + "px";
I would prefer to use appendChild because then you can create your new dom elements in memory and add it with this method.
.innerHtml is also working but then you're more working with strings.
Please have a look at the demo below or this fiddle.
Vue.js is not really needed in this example but with it's easier to work component based. If you don't know Vue then just have a look at the add method that's called by the mousedown eventhandler markImage.
As O'Kane mentioned in the comments, you need to be careful with your string concatenation. I'm using back-tick syntax / template string for the strings in my demo so you can easily insert your values into the string. e.g.
var style = `
left: ${pos.x}px;
top: ${pos.y}px;`
The same is also possible with single quotes but that's harder to write. e.g.
var style = 'left: ' + pos.x + 'px; top: ' + pos.y + 'px;'
Note: Babel is required to work with template strings. With-out it browser support is limited see here
In the demo I've created two variants:
SVG rectangles (less css required to make it work)
DIVs like your code (requires more css)
I think both variants are working and OK. I would probably use SVG because it's easier to create and also exporting later is no problem. e.g. if you like to save it as a new image.
A note to the css
The following zero-space width character is needed to have the div react on the size specified. Probably setting min-width would also work.
.image-marker-png:after {
content: '\200b';
}
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
const svgOutput = {
mounted () {
this.svg = this.$el.querySelector('svg')
let img = document.createElementNS('http://www.w3.org/2000/svg', 'image')
img.setAttribute('x', 0)
img.setAttribute('y', 0)
img.setAttribute('width', '100%')
img.setAttribute('height', '100%')
img.setAttribute('href', 'https://unsplash.it/300')
console.log(img)
this.svg.appendChild(img)
// setInterval(this.add, 50)
this.image = this.svg.querySelector('image')
this.image.addEventListener('mousedown', this.markImage)
},
beforeDestroy() {
// clean listeners
this.image.removeEventListener('mousedown', this.markImage)
},
template: `
<div>
<svg width="300px" height="300px" xmlns="http://www.w3.org/2000/svg"></svg>
<button #click="add">add random</button>
<button #click="add($event, {x: 20, y: 40}, {width: 50, height: 100})">
add "x20,y40,w50,h100"
</button>
</div>
`,
methods: {
add (evt, pos, dimension, color) {
let rectangle = document.createElementNS("http://www.w3.org/2000/svg", 'rect')
let containerSize = this.svg.getBoundingClientRect();
dimension = dimension || {}
pos = pos || {}
let sizeX = dimension.width || 50;
let sizeY = dimension.height || 50;
let x = pos.x || (Math.random()*(containerSize.width) - sizeX)
let y = pos.y || (Math.random()*(containerSize.height) - sizeY)
// some limiting
if (x > containerSize.width) {
x = containerSize.width - sizeX + 1
}
if (x < 0) {
x = 1
}
if (y > containerSize.height) {
y = containerSize.height - sizeY + 1
}
if (y < 0) {
y = 1
}
rectangle.setAttribute('class', 'image-marker')
rectangle.setAttribute('width', sizeX)
rectangle.setAttribute('height', sizeY)
rectangle.setAttribute('x', x)
rectangle.setAttribute('y', y)
rectangle.setAttribute('fill', color || getRandomColor())
rectangle.setAttribute('stroke', 'black')
this.svg.appendChild(rectangle)
},
markImage (evt) {
console.log('clicked svg', evt)
this.add(evt, {x: evt.offsetX, y: evt.offsetY}, {width: 20, height: 20})
}
}
}
const imgOutput = {
beforeDestroy() {
// clean listeners
this.container.removeEventListener('mousedown', this.markImage)
},
mounted() {
this.container = this.$el
this.container.addEventListener('mousedown', this.markImage)
},
template: `
<div class="image-container">
<img src="https://unsplash.it/300"/>
</div>
`,
methods: {
add (evt, pos, dimension, color) {
let marker = document.createElement('div')
marker.setAttribute('class', 'image-marker-png')
marker.setAttribute('style', `
left: ${pos.x}px;
top: ${pos.y}px;
width: ${dimension.width}px;
height: ${dimension.height}px;
background-color: ${color || getRandomColor()}`)
console.log('add marker', marker)
this.container.appendChild(marker)
},
markImage (evt) {
console.log('clicked image', evt)
this.add(evt, {x: evt.offsetX, y: evt.offsetY}, {width: 20, height: 20})
}
}
}
console.log(imgOutput)
new Vue({
components: {
svgOutput,
imgOutput
},
el: '#app'
})
.image-marker:hover {
stroke-width: 1px;
stroke: yellow;
}
.image-container {
position: relative;
display: inline-block;
overflow: hidden;
}
.image-marker-png {
position: absolute;
z-index: +1;
}
.image-marker-png:hover{
border: 1px solid yellow;
}
.image-marker-png:after {
content: '\200b';
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<svg-output></svg-output>
<img-output></img-output>
</div>
Related
I need to make it so that it is possible to connect the dice only with straight lines. But to do this, I need each subsequent dice to have a common "top" or "left" coordinate with the previous dice and at the same time a partial random is preserved.
Here is an example:
Here is my code:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Игральные кости</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<link rel="stylesheet" type="text/css" href="./style.css">
<link rel="icon" type="image/png" href="./dice.png">
</head>
<body>
<div id="main-container">
<div id="square-container">
</div>
<canvas id="overlay"></canvas>
</div>
</body>
<script src="./script.js"></script>
</html>
html,
body {
margin: 0;
padding: 0;
}
#main-container {
width: 100%;
margin: 10px
}
#square-container {
width: 700px;
height: 600px;
position: absolute;
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
background: #dddddd;
border: 5px solid #6b6b6b;
border-radius: 5px;
z-index: 1;
}
#overlay {
width: 100%;
height: 100%;
pointer-events: none;
z-index: 9999;
position: absolute;
top: 0;
left: 0;
}
.js-is-target {
position: absolute;
width: 56px;
height: 56px;
/*background: blue;*/
border-radius: 30px;
}
.square {
top: 13px;
left: 13px;
position: relative;
width: 30px;
height: 30px;
background: red;
background-size: contain;
border-radius: 5px;
z-index: 10;
}
.active-target {
box-shadow: 0 0 0 2px rgba(255, 0, 0, 0.5);
}
//links to images of dice
var imgArr = [
'./dotsOnDice/dice1.jpg',
'./dotsOnDice/dice2.jpg',
'./dotsOnDice/dice3.jpg',
'./dotsOnDice/dice4.jpg',
'./dotsOnDice/dice5.jpg',
'./dotsOnDice/dice6.jpg'
]
//random number from 3 to 6
var randomAmount = Math.floor(Math.random() * (6 - 3 + 1)) + 3;
//limit for recursion
var limitDice = 0;
function addDice() {
// if the limit is equal to a random number, the recursion function is interrupted
if (limitDice === randomAmount) {
return
}
//random number from 2 to 5
let randomAmountImg = Math.floor(Math.random() * (5 - 2 + 1)) + 2;
//random numbers for dice coordinates
let position = {
top: Math.random() * 520,
left: Math.random() * 600
}
//saved previous coordinates for comparison
let positionAfter = {
top: 0,
left: 0
}
//creating an id that we press later
let idSquare = 'idSquare-' + limitDice;
//subtract the previous coordinates from the current coordinates
let top = position.top - positionAfter.top;
let left = position.left - positionAfter.left;
//we compare the positions of the previous coordinates to avoid a large number of overlaps
if (top > 50 || left > 50) {
positionAfter.top = position.top;
positionAfter.left = position.left;
//adding dice to the playing field
let square = $('<div class="js-is-target"><div id="' + idSquare + '" class="square"></div></div>');
square.appendTo('#square-container').css({ left: position.left + 'px', top: position.top + 'px' });
if (limitDice === 0) {
$('#idSquare-0').css('background-image', 'url(' + imgArr[randomAmountImg] + ')');
} else if (limitDice === 1) {
$('#idSquare-1').css('background-image', 'url(' + imgArr[randomAmountImg] + ')');
} else if (limitDice === 2) {
$('#idSquare-2').css('background-image', 'url(' + imgArr[randomAmountImg] + ')');
} else if (limitDice === 3) {
$('#idSquare-3').css('background-image', 'url(' + imgArr[randomAmountImg] + ')');
} else if (limitDice === 4) {
$('#idSquare-4').css('background-image', 'url(' + imgArr[randomAmountImg] + ')');
} else if (limitDice === 5) {
$('#idSquare-5').css('background-image', 'url(' + imgArr[randomAmountImg] + ')');
}
limitDice += 1;
}
addDice();
}
addDice();
//here we store the canvas field
const canvas = document.getElementById('overlay');
//here we set the format in 2D for canvas
const ctx = canvas.getContext('2d');
//here we store the width and height of the canvas field. We set the height and width in HTML
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
//in the variable we store the element that we clicked on
let targetEl = null;
//in the variable we store the coordinates of the center of the element we clicked on
let targetCenter = null;
//we check whether you clicked on the mouse or not
var down = false;
//coordinates of the pressed cursor mouseCoords
var mouseCoords = {
x: 0,
y: 0
}
//coordinates of the starting point
var centerCoords = {
x: 760,
y: 280
}
//в этой функции мы вычисляем где центр у прямоугольника
function getCenter(el) {
//if not true, the function terminates and returns null
if (!el) return null;
//we save the coordinates in the rect constant using the getBoundingClientRect method
const rect = el.getBoundingClientRect();
//returning the x and y coordinates. Here we calculate the center of the rectangle
return {
x: rect.left + rect.width / 2 + window.scrollX,
y: rect.top + rect.height / 2 + window.scrollY,
}
}
//connection points
function joinPoints(ctx, from, to) {
//dotted line size
const dashSize = 10;
//draw a dotted line and write the size in the arguments
ctx.setLineDash([dashSize]);
//setting the gradient
var stroke = ctx.createLinearGradient(from.x, from.y, to.x, to.y);
//setting the color and position
stroke.addColorStop(0, 'blue');
stroke.addColorStop(1, 'red');
//passing the colors to the strokeStyle property to set the color
ctx.strokeStyle = stroke;
//line thickness
ctx.lineWidth = 5;
//draw a contour (line)
ctx.beginPath();
//moves the contour point to the specified coordinates without drawing a line. Starting point
ctx.moveTo(from.x, from.y);
//adds a new contour point and creates a line to this point from the last specified point. End point
ctx.lineTo(to.x, to.y);
//draw a line
ctx.stroke();
}
//when you click the mouse
$(document).mousedown(function (e) {
down = true;
mouseCoords.x = e.pageX;
mouseCoords.y = e.pageY;
});
//when moving the mouse
$(document).mousemove(function (event) {
if (down) {
mouseCoords.x = event.pageX;
mouseCoords.y = event.pageY;
console.log(mouseCoords.x + ' ' + mouseCoords.y);
}
});
//when we release the mouse button
$(document).mouseup(function (e) {
down = false;
});
$(".js-is-target").hover(function (e) {
if (down) {
down = false;
//we transmit data about which js-is-target we clicked on
targetEl = e.target;
//we write down where the center of the object we clicked on is. We calculate using the getCenter() function
targetCenter = getCenter(targetEl);
//changing the coordinates of the starting point
centerCoords.x = targetCenter.x;
centerCoords.y = targetCenter.y;
}
});
//first we count, then we draw a line from ctx.lineTo to ctx.moveTo
function drwaDot() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = 'red';
ctx.moveTo(mouseCoords.x, mouseCoords.y);
ctx.lineTo(centerCoords.x, centerCoords.y);
ctx.stroke();
};
//the drawing interval of our line
var intervalId = setInterval(function () {
if (down) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
joinPoints(ctx, { x: centerCoords.x, y: centerCoords.y }, mouseCoords);
} else {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
}, 10);
jsfiddle link
under the comment "//we compare the positions of the previous coordinates to avoid a large number of overlaps" changed the condition:
if(top < 10 || left < 10){
dice spawn code
}
That's what came out of it:
Problem is simple but seems I can't get over it, div called ".player" shouldn't be going out from div ".play-ground", it should be moving only in its yellow space. Next thing which I'm trying to achieve is that I want a div same size of the red one, showing up for some time, so that the user can catch it with the red one and after that he gets points otherwise doesn't. Is there any way to do it? And how make div disapear after being catched?
var a = prompt("Provide nick");
while (a === "") {
a = prompt("Provide nick");
}
document.write("<p>Nick: " + a + "</p>");
/* -------- */
var ground = document.getElementsByClassName('play-ground')[0];
var player = document.getElementsByClassName('player')[0];
var points = document.getElementsByClassName('numba')[0];
var thing = document.getElementsByClassName('thing-tocatch')[0];
document.onkeydown = move;
var lefts = 0;
var tops = 0;
function move(e) {
console.log(e.keyCode);
if (e.keyCode == 68) {
lefts += 100;
player.style.left = lefts + "px";
points.innerHTML = lefts;
}
if (e.keyCode == 65) {
lefts -= 100;
player.style.left = lefts + "px";
}
if (e.keyCode == 83) {
tops += 100;
player.style.top = tops + "px";
}
if (e.keyCode == 87) {
tops -= 100;
player.style.top = tops + "px";
}
}
.play-ground {
position: relative;
background: yellow;
width: 800px;
height: 700px;
}
.player {
position: absolute;
background: red;
width: 100px;
height: 100px;
}
.thing-tocatch {
background: blue;
height: 100px;
width: 100px;
position: absolute;
margin: 200px 200px;
}
<div id="game">
<div class="points">
<h3>Points: <span class="numba">0</span></h3>
</div>
<div class="play-ground">
<div class="thing-tocatch">
</div>
<div class="player">
</div>
</div>
</div>
</div>
I cannot write whole the app here, but I just want to show you some things.
Try to write your code: one action - one function. You can see as I did. I've splitted up your move function on two: onKeyDown and movePlayer. It will help you to support your code when the app will become bigger.
You should store states of your subjects. For example I've created object Palyer where I store his coords (you used for that two vars: lefts and tops). It will be better if you will implement the same for all subjects in your app. For example, the Player has props: width, height, x, y, color. The ground has props: width, height, color. The thing also should have props as for player. You should store these states as global variables, because you will need to use them in different functions.
Your questions:
Next thing which I'm trying to achieve is that I want a div same size of the red one, showing up for some time, so that the user can catch it with the red one and after that he gets points otherwise doesn't. Is there any way to do it?. Your question contains answer. You need a function which will draw (document.createElement('div')) HTML element and after that you will be able to change its styles. OR you can create the thing before start (as you did with player) and keep it hidden (style display: none;)
And how make div disapear after being catched? You can remove thing from DOM or change style to display: none;
I advice you to find similar apps to check how other developers implement similar things:
Pong Clone In JavaScript
Snake Game in vanilla js
var a = prompt("Provide nick");
while (a === "") {
a = prompt("Provide nick");
}
document.write("<p>Nick: " + a + "</p>");
/* -------- */
var ground = document.getElementsByClassName('play-ground')[0];
var player = document.getElementsByClassName('player')[0];
var points = document.getElementsByClassName('numba')[0];
var thing = document.getElementsByClassName('thing-tocatch')[0];
document.onkeydown = onKeyDown;
var Player = {x: 0, y: 0};
var STEP = 100;
function onKeyDown (e) {
console.log(e.keyCode);
var x = Player.x;
var y = Player.y;
if (e.keyCode == 68) {
x += STEP;
}
if (e.keyCode == 65) {
x -= STEP;
}
if (e.keyCode == 83) {
y += STEP;
}
if (e.keyCode == 87) {
y -= STEP;
}
movePlayer(x, y);
}
function movePlayer (x, y) {
if (!isCoordsInsideGround(x, y)) return;
Player.x = x || 0;
Player.y = y || 0;
player.style.left = Player.x + "px";
player.style.top = Player.y + "px";
}
function isCoordsInsideGround (x, y) {
if (x < 0 || ground.offsetWidth - STEP < x) {
return false;
}
if (y < 0 || ground.offsetHeight - STEP < y) {
return false;
}
return true;
}
.play-ground {
position: relative;
background: yellow;
width: 800px;
height: 700px;
}
.player {
position: absolute;
background: red;
width: 100px;
height: 100px;
}
.thing-tocatch {
background: blue;
height: 100px;
width: 100px;
position: absolute;
margin: 200px 200px;
}
<div id="game">
<div class="points">
<h3>Points: <span class="numba">0</span></h3>
</div>
<div class="play-ground">
<div class="thing-tocatch">
</div>
<div class="player">
</div>
</div>
</div>
</div>
I'm using a bit of HTML & CSS on my squarespace site to create a custom follow cursor. I want to just have a floaty circle with no actual cursor displayed. I've gotten it to mostly work, but when my site scrolls the follow cursor doesn't move with the page scroll and just gets stuck at the top.
And that just caused the follow cursor to stop moving with mouse movement entirely, becoming static on the center of the page.
Injecting HTML & CSS on to squarespace site to create a custom follow cursor:
body {
background: #161616;
}
.wrap {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
overflow: hidden;
}
#ball {
width: 60px;
height: 60px;
background: none;
border: 1px solid grey;
border-radius: 50%;
position: absolute;
left: 50%;
top: 50%;
margin: -10px 0 0 -10px;
pointer-events: none;
}
<body onload="followMouse();">
<div class="wrap">
<div id="ball"></div>
</div>
<script type="text/javascript">
var $ = document.querySelector.bind(document);
var $on = document.addEventListener.bind(document);
var xmouse, ymouse;
$on('mousemove', function (e) {
xmouse = e.clientX || e.pageX;
ymouse = e.clientY || e.pageY;
});
var ball = $('#ball');
var x = void 0,
y = void 0,
dx = void 0,
dy = void 0,
tx = 0,
ty = 0,
key = -1;
var followMouse = function followMouse() {
key = requestAnimationFrame(followMouse);
if(!x || !y) {
x = xmouse;
y = ymouse;
} else {
dx = (xmouse - x) * 0.125;
dy = (ymouse - y) * 0.125;
if(Math.abs(dx) + Math.abs(dy) < 0.1) {
x = xmouse;
y = ymouse;
} else {
x += dx;
y += dy;
}
}
ball.style.left = x + 'px';
ball.style.top = y + 'px';
};
</script>
</body>
[EDIT] Great job on updating your question, the demo and the problem are very clear now. Don't worry about your demo not scrolling, I just added a bunch of divs with some height in my demo to simulate that. Here's everything you need to / should change to make it all work:
var followMouse = function followMouse() ... is very strange syntax and I'm not sure what the exact outcome will be.
Either declare the function normally function followMouse() ..., or store it in a variable using either the:
function definition var followMouse = function() ... or
arrow definition var followMouse = () => ...
To simply get it all working you just need to adjust for the current scroll amount of either the document or in my demo's case the element with class ".wrap".
This can be done using the scrollTop member of the object returned by your $() function.
I started by just adding $(".wrap").scrollTop to the ymouse variable in the mousemove listener, but while this works it needs you to move the mouse for the circle to realize it's scrolled off the page.
So instead we just add $(".wrap").scrollTop to the css that is being set to the ball in the last lines of followMouse.
I changed the overflow property from hidden to scroll since that's kind of where the problem is occuring ;)
I've also added cursor: none to your .wrap css so that you get the desired effect of no cursor but your custom one.
var $ = document.querySelector.bind(document);
var $on = document.addEventListener.bind(document);
var followMouse = function() {
key = requestAnimationFrame(followMouse);
if (!x || !y) {
x = xmouse;
y = ymouse;
} else {
dx = (xmouse - x) * 0.125;
dy = (ymouse - y) * 0.125;
if (Math.abs(dx) + Math.abs(dy) < 0.1) {
x = xmouse;
y = ymouse;
} else {
x += dx;
y += dy;
}
}
ball.style.left = x + 'px';
ball.style.top = $(".wrap").scrollTop + y + 'px';
};
var xmouse, ymouse;
var ball = $('#ball');
var x = void 0,
y = void 0,
dx = void 0,
dy = void 0,
tx = 0,
ty = 0,
key = -1;
$on('mousemove', function(e) {
xmouse = e.clientX || e.pageX;
ymouse = e.clientY || e.pageY;
});
body {
background: #161616;
}
.wrap {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
overflow: scroll;
cursor: none;
}
#ball {
width: 60px;
height: 60px;
background: none;
border: 1px solid grey;
border-radius: 50%;
position: absolute;
left: 50%;
top: 50%;
margin: -10px 0 0 -10px;
pointer-events: none;
}
.makeOverflow {
width: 100px;
height: 300px;
}
<body onload="followMouse();">
<div class="wrap">
<div id="ball"></div>
<div class="makeOverflow"> </div>
<div class="makeOverflow"> </div>
<div class="makeOverflow"> </div>
<div class="makeOverflow"> </div>
</div>
</body>
This will probably be fixed by changing the #ball CSS from being absolutely positioned to a fixed position, then it should scroll down the page with your original js
I need to have a cursor to drag and take screenshot of dragged area on HTML webpage. I tried using HTML canvas but it takes screenshot of specific div not the selected region on HTML webpage.
The new html2canvas version 1 has width, height, x and y options.
You can make use of these options to achieve a cropping feature the Firefox's Screenshot's way.
document.onmousedown = startDrag;
document.onmouseup = endDrag;
document.onmousemove = expandDrag;
var dragging = false,
dragStart = {
x: 0,
y: 0
},
dragEnd = {
x: 0,
y: 0
};
function updateDragger() {
dragger.classList.add('visible');
var s = dragger.style;
s.top = Math.min(dragStart.y, dragEnd.y) + 'px';
s.left = Math.min(dragStart.x, dragEnd.x) + 'px';
s.height = Math.abs(dragStart.y - dragEnd.y) + 'px';
s.width = Math.abs(dragStart.x - dragEnd.x) + 'px';
}
function startDrag(evt) {
evt.preventDefault();
dragging = true;
dragStart.x = dragEnd.x = evt.clientX;
dragStart.y = dragEnd.y = evt.clientY;
updateDragger();
}
function expandDrag(evt) {
if (!dragging) return;
dragEnd.x = evt.clientX;
dragEnd.y = evt.clientY;
updateDragger();
}
function endDrag(evt) {
dragging = false;
dragger.classList.remove('visible');
// here is the important part
html2canvas(document.body, {
width: Math.abs(dragStart.x - dragEnd.x),
height: Math.abs(dragStart.y - dragEnd.y),
x: Math.min(dragStart.x, dragEnd.x),
y: Math.min(dragStart.y, dragEnd.y)
})
.then(function(c) {
document.body.appendChild(c);
});
dragStart.x = dragStart.y = dragEnd.x = dragEnd.y = 0;
}
* {
user-select: none;
}
#dragger {
position: fixed;
background: rgba(0, 0, 0, .5);
border: 1px dashed white;
pointer-events: none;
display: none;
}
#dragger.visible {
display: block;
}
canvas {
border: 1px solid;
}
<script src="https://github.com/niklasvh/html2canvas/releases/download/v1.0.0-alpha.1/html2canvas.js"></script>
<div id="wrapper">
<p> Drag to take a screenshot ...</p>
<img crossOrigin src="https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png" width="120" height="120">
</div>
<div id="dragger" tabindex></div>
The Goal :
The idea is to create an element grid (image gallery for exemple) that would infinitely loop on itself scrolling on two axes.
There should be no holes nor too much randomness (avoid having the same element randomly falling aside from itself). And this no matter how many element there is in the first place (it seems easy to infinite loop through a grid of 16 (4*4) elements, not that much over 17 (17*1). (My guess is that any prime number of elements is by definition a pain to make a grid of).
So I actually found a wonderful working exemple :
http://www.benstockley.com/
It's actually really close (probably better) than what I was imagining. Now it's using canvas and i tried looking at the javascript and it's a 30000 minified lines long script so I really can't read any core logic behind it.
Math side / Problem solving :
This is the logic and theory behind the problem, the math involved and the mindset.
How the program should process the list of elements so we have no holes, infinite grid, best repartion of the elements over all the axes.
My guess is that it somehow has to be procedural. I'm not sure if we should create grids or loop through the list on every axes (kind of like sudoku ? i don't know);
Pratical side / UI / UX :
Any advice on the technologies involved, pieces of code. I'm guessing it classic DOM is out of the way and that somehow canvas or 2D webgl will be mandatory. But I would love to hear any advice on this side.
Besides all the elements grid processing. The UI and UX involved in exploring a 2D infinite or vast layout in DOM or renderer is somehow not classical. The best technologies or advice on doing this are welcome.
Exemples :
I would welcome any working exemple that somewhat share an aspect of this problem.
I've got a fiddle that's set up to arrange your 2d grid.
It functions by using horizontal and vertical "step sizes". So, moving one step right in the grid advances the horizontal step size in the list. Moving one step down advances the vertical step size in the list (and they accumulate).
We allow the advances in the list to loop back to zero when the end is reached.
It likely makes sense to use a horizontal step size of 1 (so a row of your grid will maintain your list order). For the vertical step size, you want an integer that shares no common divisors with the list length. Though it's no guarantee, I used the (rounded) square root of the list length as something that will work in lots of cases.
I'll reproduce the fiddle here:
var list = ['red','green','blue','cyan','orange','yellow','pink'];
var hstep = 1;
var vstep = Math.ceil(Math.sqrt(list.length));
function getListItem(x,y) {
var index = x * hstep + y * vstep;
return list[index % list.length];
}
var elementSize = 30;
var gutterSize = 10;
function getOffset(x,y) {
return [10 + (elementSize + gutterSize) * x, 10 + (elementSize + gutterSize) * y];
}
var frame = $('.frame');
function drawElement(x,y) {
var listItem = getListItem(x,y);
var offsets = getOffset(x,y);
var element = $('<div></div>').addClass('element').css({
left: offsets[0] + 'px',
top: offsets[1] + 'px',
'background-color': listItem
});
frame.append(element);
}
function drawElements() {
var x = 0, y = 0;
while (10 + (elementSize + gutterSize) * x < frame.width()) {
while (10 + (elementSize + gutterSize) * y < frame.height()) {
drawElement(x,y);
y++;
}
y = 0;
x++;
}
}
drawElements();
.frame {
border: 2px solid black;
margin: 40px auto;
height: 300px;
width: 300px;
position: relative;
overflow: hidden;
}
.frame .element {
position: absolute;
width: 30px;
height: 30px;
}
.buttons {
position: absolute;
top: 0px;
width: 100%;
}
.buttons button {
position: absolute;
width: 30px;
height: 30px;
padding: 5px;
}
button.up {top: 0px; left: 46%;}
button.down {top: 355px; left: 46%;}
button.left {top: 160px; left: 15px;}
button.right {top: 160px; right: 15px;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="frame">
</div>
<div class="buttons">
<button class="up">↑</button>
<button class="down">↓</button>
<button class="left">←</button>
<button class="right">→</button>
</div>
You can see I've left some simple buttons to implement movement, but they are not functional yet. If you wanted to continue implementation along the lines of what I've done here, you could render your elements to a certain range beyond the visible frame, then implement some sort of animated repositioning. The renderElements function here only renders what is visible, so you can use something like that and not get stuck in rendering infinite elements, even though there's no theoretical limit to how far you can "scroll".
#arbuthnott I edited your code to implement the exploration via decrementing relativeX and relativeY variables. Also I inserted an "origin" div (1x1 px, overflow visible). This DOM element will represent the X and Y origin. I'm not sure it's essential but it's really convenient.
Now my function currently remove all elements and reinsert all elements on each update (every 500ms for now).
The idear would be to find a way to compare which elements I need versus which one already exists.
Maybe storing existing elements into an array, and compare the array with the "query" array. Than see just the elements that are missing.
This is the idear, not sure about the implementation (I suck at handling arrays).
https://jsfiddle.net/bnv6mumd/64/
var sources = ['red','green','blue','cyan','orange','yellow','pink','purple'];
var frame = $('.frame'),
origin = $('.origin');
var fWidth = 600,
fHeight = 300,
srcTotal = sources.length,
srcSquare = Math.ceil(Math.sqrt(srcTotal)),
rX = 0,
rY = 0;
var gridSize = 30,
gutterSize = 5,
elementSize = gridSize - gutterSize;
function getSourceItem(x,y) {
var index = x + y * srcSquare;
return sources[Math.abs(index) % srcTotal];
}
function getOffset(x,y) {
return [gridSize * x,gridSize * y];
}
function drawElement(x,y) {
var sourceItem = getSourceItem(x,y);
var offsets = getOffset(x,y);
var element = $('<div></div>').addClass('element').css({
left: offsets[0] + 'px',
top: offsets[1] + 'px',
'background-color': sourceItem,
});
origin.append(element);
}
function init() {
var x = 0, y = 0;
while ( gridSize * x < fWidth) {
while ( gridSize * y < fHeight) {
drawElement(x,y);
y++;
}
y = 0;
x++;
}
}
function updateElements() {
origin.empty();
var x = -Math.trunc(rX / gridSize) -1, y = - Math.trunc(rY / gridSize) -1;
while ( gridSize * x + rX < fWidth) {
while ( gridSize * y + rY < fHeight) {
drawElement(x,y);
y++;
}
y = -Math.ceil(rY / gridSize);
x++;
}
}
function animate() {
rX -= 5;
rY -= 5;
origin.css({left: rX, top: rY})
updateElements();
console.log("relative X : " + rX + " | relative Y : " + rY);
}
setInterval(animate, 500)
init();
.frame {
border: 2px solid black;
margin: 40px auto;
height: 300px;
width: 600px;
position: relative;
overflow: hidden;
}
.origin {
height: 1px;
width: 1px;
position: absolute;
overflow: visible;
}
.frame .element {
position: absolute;
width: 25px;
height: 25px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="frame">
<div class="origin" style="top:0;left:0;"></div>
</div>
This is my final snippet version (i will start to work on real implementation specific to my case now).
I think I optimized in a decent way DOM operations, code structure etc (I am very well open to suggestions though).
I now only update the elements that needs to be updated (click near the frame to show overflow)
https://jsfiddle.net/bnv6mumd/81/
var sources = ['red', 'green', 'blue', 'cyan', 'orange', 'yellow', 'pink', 'purple'];
var frame = $('.frame'),
origin = $('.origin');
var srcTotal = sources.length,
srcSquare = Math.round(Math.sqrt(srcTotal)),
fWidth = 200,
fHeight = 200,
cellSize = 50,
gutterSize = 20,
gridSize = [Math.floor(fWidth / cellSize) + 1, Math.floor(fHeight / cellSize) + 1],
aX = 0, // Absolute/Applied Coordinates
aY = 0,
rX = 0, // Relative/frame Coordinates
rY = 0;
function getSrcItem(x, y) {
var index = x + y * srcSquare;
return sources[Math.abs(index) % srcTotal];
}
function getOffset(x, y) {
return [cellSize * x, cellSize * y];
}
function getY() {
return Math.floor(-rY / cellSize);
}
function getX() {
return Math.floor(-rX / cellSize);
}
function drawElement(x, y) {
var srcItem = getSrcItem(x, y),
offsets = getOffset(x, y),
element = $('<div></div>').addClass('element').css({
left: offsets[0] + 'px',
top: offsets[1] + 'px',
'background-color': srcItem,
}).attr({
"X": x,
"Y": y
});
origin.append(element);
}
function drawCol(x, y) {
var maxY = y + gridSize[1];
while (y <= maxY + 1) {
drawElement(x - 1, y - 1);
y++;
}
}
function drawLign(x, y) {
var maxX = x + gridSize[0];
while (x <= maxX + 1) {
drawElement(x - 1, y - 1);
x++;
}
}
function drawGrid() {
origin.empty();
var x = getX(),
y = getY(),
maxX = x + gridSize[0],
maxY = y + gridSize[1];
while (y <= maxY + 1) {
drawLign(x, y);
x = getX();
y++;
}
}
function updateX(x, y, diffX, diffY) {
if (Math.sign(diffX) == -1) {
drawCol(aX - 1, y);
$('[x=' + (aX + gridSize[0]) + ']').remove();
aX--;
} else if (Math.sign(diffY) == 1) {
drawCol(aX + gridSize[0] + 2, y);
$('[x=' + (aX - 1) + ']').remove();
aX++;
}
}
function updateY(x, y, diffX, diffY) {
if (Math.sign(diffY) == -1) {
drawLign(x, aY - 1);
$('[y=' + (aY + gridSize[0]) + ']').remove();
aY--;
} else if (Math.sign(diffY) == 1) {
drawLign(x, aY + gridSize[0] + 2);
$('[y=' + (aY - 1) + ']').remove();
aY++;
}
}
function animate() {
rX += 1;
rY += 1;
origin.css({
left: rX,
top: rY
});
var x = getX(),
y = getY(),
diffX = x - aX,
diffY = y - aY;
if (diffX) {
updateX(x, y, diffX, diffY)
};
if (diffY) {
updateY(x, y, diffX, diffY)
};
requestAnimationFrame(animate);
}
$('body').click(function() {
$(frame).toggleClass("overflow");
})
drawGrid();
animate();
.frame {
border: 2px solid black;
margin: 100px auto;
height: 200px;
width: 200px;
position: relative;
}
.overflow{
overflow:hidden;
}
.origin {
height: 1px;
width: 1px;
position: absolute;
overflow: visible;
}
.frame .element {
position: absolute;
width: 30px;
height: 30px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="frame overflow">
<div class="origin" style="top:0;left:0;"></div>
</div>