2D Infinitely looping Array of elements - javascript

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>

Related

How can I effectively pan an entire image programmatically?

I have a 11500x11500 div that consists of 400 images, that obviously overflows the viewport.
I would like to pan around the whole div programmatically.
I want to generate an animation and by the time the animation is over, the whole of the div must have been panned across the viewport, top to bottom, left to right.
Right now, I am "splitting" my 11500x1500 div into tiles. The maximum width and height of each tile is the width and height of the viewport.
I store the coordinates of each tile and then I randomly choose one, pan it left-to-right and then move on to the next one.
I would like to know:
whether my method is correct or whether I am missing something in my calculations/approach and it could be improved. Given the size, it is hard for me to tell whether I'm actually panning the whole of the div after all
whether I can make the panning effect feel more "organic"/"natural". In order to be sure that the whole div is eventually panned, I pick each tile and pan it left-to-right, move on to the next one etc. This feels kind of rigid and too formalised. Is there a way to pan at let's say an angle or with a movement that is even more random and yet be sure that the whole div will eventually be panned ?
Thank in advance for any help.
This is the jsfiddle and this is the code (for the sake of the example/test every "image" is actually a div containing its index as text):
function forMs(time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, time)
})
}
let container = document.getElementById('container')
let {
width,
height
} = container.getBoundingClientRect()
let minLeft = window.innerWidth - width
let minTop = window.innerHeight - height
let i = 0
while (i < 400) {
// adding "image" to the container
let image = document.createElement('div')
// add some text to the "image"
// to know what we're looking at while panning
image.innerHTML = ''
let j = 0
while (j < 100) {
image.innerHTML += ` ${i + 1}`
j++
}
container.appendChild(image)
i++
}
let coords = []
let x = 0
while (x < width) {
let y = 0
while (y < height) {
coords.push({
x,
y
})
y += window.innerHeight
}
x += window.innerWidth
}
async function pan() {
if (!coords.length) {
return;
}
let randomIdx = Math.floor(Math.random() * coords.length)
let [randomCoord] = coords.splice(randomIdx, 1);
console.log(coords.length)
container.classList.add('fast')
// update style in new thread so new transition-duration is applied
await forMs(10)
// move to new yet-unpanned area
container.style.top = Math.max(-randomCoord.y, minTop) + 'px'
container.style.left = Math.max(-randomCoord.x, minLeft) + 'px'
// wait (approx.) for transition to end
await forMs(2500)
container.classList.remove('fast')
// update style in new thread so new transition-duration is applied
await forMs(10)
//pan that area
let newLeft = -(randomCoord.x + window.innerWidth)
if (newLeft < minLeft) {
newLeft = minLeft
}
container.style.left = newLeft + 'px'
// wait (approx.) for transition to end
await forMs(4500)
// move on to next random area
await pan()
}
pan()
html,
body {
position: relative;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: auto;
}
* {
margin: 0;
padding: 0;
}
#container {
position: absolute;
top: 0;
left: 0;
text-align: left;
width: 11500px;
height: 11500px;
transition: all 4s ease-in-out;
transition-property: top left;
font-size: 0;
}
#container.fast {
transition-duration: 2s;
}
#container div {
display: inline-block;
height: 575px;
width: 575px;
border: 1px solid black;
box-sizing: border-box;
font-size: 45px;
overflow: hidden;
word-break: break-all;
}
<div id="container"></div>
I think following improvements can be made:
Hide overflow on html and body so user can not move scrollbar and disturb the flow.
Calculate minLeft and minTop every time to account for window resizing. You might need ResizeObserver to recalculate things.
Increase transition times to avoid Cybersickness. In worse case RNG will pick bottom right tile first so your container will move the longest in 2seconds! Maybe, you can zoom-out and move then zoom-in then perform pan. Or use any serpentine path which will make shorter jumps.
Performance improvements:
Use transform instead of top, left for animation.
Use will-change: transform;. will-change will let browser know what to optimize.
Use translate3D() instead of translate(). ref
Use requestAnimationFrame. Avoid setTimeout, setInterval.
This is an old but good article: https://www.paulirish.com/2012/why-moving-elements-with-translate-is-better-than-posabs-topleft/
Modified code to use transform:
function forMs(time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, time)
})
}
let container = document.getElementById('container')
let stat = document.getElementById('stats');
let {
width,
height
} = container.getBoundingClientRect()
let minLeft = window.innerWidth - width
let minTop = window.innerHeight - height
let i = 0
while (i < 400) {
// adding "image" to the container
let image = document.createElement('div')
// add some text to the "image"
// to know what we're looking at while panning
image.innerHTML = ''
let j = 0
while (j < 100) {
image.innerHTML += ` ${i + 1}`
j++
}
container.appendChild(image)
i++
}
let coords = []
let x = 0
while (x < width) {
let y = 0
while (y < height) {
coords.push({
x,
y
})
y += window.innerHeight
}
x += window.innerWidth
}
let count = 0;
async function pan() {
if (!coords.length) {
stat.innerText = 'iteration: ' +
(++count) + '\n tile# ' + randomIdx + ' done!!';
stat.style.backgroundColor = 'red';
return;
}
let minLeft = window.innerWidth - width
let minTop = window.innerHeight - height
let randomIdx = Math.floor(Math.random() * coords.length);
randomIdx = 1; //remove after debugging
let [randomCoord] = coords.splice(randomIdx, 1);
stat.innerText = 'iteration: ' +
(++count) + '\n tile# ' + randomIdx;
console.log(coords.length + ' - ' + randomIdx)
container.classList.add('fast')
// update style in new thread so new transition-duration is applied
await forMs(10)
// move to new yet-unpanned area
let yy = Math.max(-randomCoord.y, minTop);
let xx = Math.max(-randomCoord.x, minLeft);
move(xx, yy);
// wait (approx.) for transition to end
await forMs(2500)
container.classList.remove('fast')
// update style in new thread so new transition-duration is applied
await forMs(10)
//pan that area
let newLeft = -(randomCoord.x + window.innerWidth)
if (newLeft < minLeft) {
newLeft = minLeft
}
xx = newLeft;
//container.style.left = newLeft + 'px'
move(xx, yy);
// wait (approx.) for transition to end
await forMs(4500)
// move on to next random area
await pan()
}
pan()
function move(xx, yy) {
container.style.transform = "translate3D(" + xx + "px," + yy + "px,0px)";
}
html,
body {
position: relative;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#container {
text-align: left;
width: 11500px;
height: 11500px;
transition: all 4s ease-in-out;
transition-property: transform;
font-size: 0;
will-change: transform;
}
#container.fast {
transition-duration: 2s;
}
#container div {
display: inline-block;
height: 575px;
width: 575px;
border: 1px solid black;
box-sizing: border-box;
font-size: 45px;
overflow: hidden;
word-break: break-all;
}
#stats {
border: 2px solid green;
width: 100px;
background-color: lightgreen;
position: fixed;
opacity: 1;
top: 0;
left: 0;
z-index: 10;
}
<div id=stats>iteration: 1 tile# 11</div>
<div id="container"></div>
Note I haven't implemented everything in above snippet.

How to make custom follow cursor follow Y axis scroll

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

Text is vertically stretched on my canvas, making it unreadable

I have a canvas that I am drawing on top of a OpenLayers map. The canvas is colored in gradient colors and now I am trying to add text on top of it.
However the text seems to be stretched making it completely unreadable.
function createSpeedBar(min, max, speeds) {
//List of integer speeds
var fullSpeeds = [];
//List of unique speed values (no duplicates)
var uniqueSpeeds = [];
//Filling fullSpeeds using minimum and maximum values
for (i = min; i <= max; i++) {
fullSpeeds.push(i);
}
//Filling uniqueSpeeds with unique values
$.each(speeds, function (i, el) {
if ($.inArray(el, uniqueSpeeds) === -1) uniqueSpeeds.push(el);
});
//Sorting uniqueSpeeds (low to high)
uniqueSpeeds = uniqueSpeeds.sort(function (a, b) {
return a - b;
});
//Getting canvas element
var canvas = document.getElementById("speedList");
var context = canvas.getContext("2d");
context.rect(0, 0, canvas.width, canvas.height);
var grd = context.createLinearGradient(0, 0, 0, 170);
var hslMin = 0;
var hslMax = 240;
//Defining hslColors using uniqueSpeeds
var hslValues = uniqueSpeeds.map(function (value) {
if ($.inArray(value, fullSpeeds)){
return {
h: Math.ceil(((value - min) / (max - min)) * (hslMax - hslMin) + hslMin),
s: 100,
l: 50,
full: true,
speed: value
};
} else {
return {
h: Math.ceil(((value - min) / (max - min)) * (hslMax - hslMin) + hslMin),
s: 100,
l: 50,
full: false
};
};
});
var count = 1;
var length = hslValues.length;
//Gradient coloring using hslColors
hslValues.forEach(function (value) {
var color = 'hsl(' + value.h + ',' + value.s + '%,' + value.l + '%)';
grd.addColorStop(count / length, color)
count += 1;
});
context.fillStyle = grd;
context.fill();
//Setting up coloring and drawing of text
count = 1
var height = canvas.height;
var width = canvas.width;
var elementHeight = height / length;
//Drawing text on canvas
hslValues.forEach(function (value) {
context.font = "12px Arial";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillStyle = "black";
if (value.full === true) {
context.fillText(value.speed, width / 2, ((elementHeight / 2) + elementHeight * count));
}
count += 1;
});
}
As it might be clear I am trying to create a bar displaying the intensities of the speed on the map where I have colored some markers. However the text on the canvas comes out like this:
Right now I have made the height of the canvas inherit the height of the map which is 500. The width of the canvas is set manually using css:
html
<div id="map" class="map">
<canvas id="speedList"></canvas>
</div>
css
#map {
position: relative;
height: 500px;
}
#speedList {
position: absolute;
right: 10px;
top: 0;
bottom: 0;
width: 20px;
z-index: 1000;
height: inherit;
margin: 0;
padding: 0;
}
I am currently working on a Fiddle, but it takes a little time to reproduce, and I bet the issue is not that big, so hopefully someone knows how to fix it before I finish the Fiddle.
The main problem here is the CSS adjustment of the width and height of the canvas element.
If it helps to understand the problem, think of <canvas> the same way you would think of <img/>, if you take an img, and give it a width and height of 50 x 500, it would stretch too.
The fix is to ensure that you set the width an height of the canvas element itself, before it processes it's content, like this:
<canvas id="speedList" width="20" height="500"></canvas>
You then also need to make sure your remove the width and height properties inside your CSS, like this:
#map {
position: relative;
height: 500px;
}
#speedList {
position: absolute;
right: 10px;
top: 0;
bottom: 0;
z-index: 1000;
margin: 0;
padding: 0;
}

How to use % instead of px with draggable element?

I'm stucked and I need help. I'm making map with markers. I can add markers etc.
My main container have 100% width and height. When I click somewhere my marker has % values for example top: 34%; left: 35%;
After dragging marker new position have px value. I want to save % values.
Any ideas?
map.js
jQuery(document).ready(function($){
// basic add
$("button#remove").click(function(){
$(".marker").remove();
});
//add with position
var map = $(".map");
var pid = 0;
function AddPoint(x, y, maps) {
// $(maps).append('<div class="marker"></div>');
var marker = $('<div class="marker ui-widget-content"></div>');
marker.css({
"left": x,
"top": y
});
marker.attr("id", "point-" + pid++);
$(maps).append(marker)
$('.marker').draggable().resizable();
}
map.click(function (e) {
// var x = e.pageX - this.offsetLeft;
// var y = e.pageY - this.offsetTop;
var x = e.offsetX/ $(this).width() * 100 + '%';
var y = e.offsetY/ $(this).height() * 100 + '%';
// $(this).append('<div class="marker"></div>');
AddPoint(x, y, this);
});
// drag function
$(".ui-widget-content").draggable({
drag: function( event, ui ) {
var x = e.offsetX/ $(this).width() * 100 + '%';
var y = e.offsetY/ $(this).height() * 100 + '%';
}
});
});
style.css
.wrapper {
margin: 0 auto;
width: 100%;
min-height: 400px;
overflow: hidden;
}
.map {
width: 100%;
position: relative;
}
.map > img {
max-width: 100%;
max-height: 100%;
}
.marker {
width: 10px;
height: 10px;
border-radius: 50%;
background: rgba(255, 0, 0, 1);
position: absolute;
left: 50%;
top: 50%;
z-index: 999;
}
#mapa {
width: 30px;
height: 30px;
border-radius: 50%;
background: rgba(255, 0, 0, 1);
z-index: 999;
}
some html code
<div class="wrapper">
<div class="map">
<img src="http://i.imgur.com/HtxXGR5.jpg" width="100%" height="auto" margin="15px 0;" alt="">
</div>
<button id="remove">Remove all markers</button>
</div>
If you want to capture the position, after the drag is ended (fire 1 time per drag) you can do this:
$('.marker').draggable({
stop: function() {
var offset = $(this).offset();
var mapOffest = $('.map').offset();
var x = ((offset.left - mapOffest.left)/ ($(".map").width() / 100))+"%";
var y = ((offset.top - mapOffest.top)/ ($(".map").height() / 100))+"%";
// We need to do something with thoses vals uh?
$(this).css('left', x);
$(this).css('top', y);
// or
console.log('X:'+x + '||Y:'+y);
}
});
Fiddle here
Take note that the event has been set on the marker directy, this was probably your mistake.
The little calcul needs to take care of the map offset, which you don't need if your map is a full width/height (window size)
If you need, or prefer use the drag event rather than the stop, theses lines won't have any effects
$(this).css('left', x);
$(this).css('top', y);
But you'll still be able to capture the position, the console values will be goods.
Hope it may helps you.
problem solved, DFayet thank's again
final code
jQuery(document).ready(function($){
// basic add
$("button#remove").click(function(){
$(".marker").remove();
});
//add with position
var map = $(".map");
var pid = 0;
function AddPoint(x, y, maps) {
var marker = $('<div class="marker"></div>');
marker.css({
"left": x,
"top": y
});
marker.attr("id", "point-" + pid++);
$(maps).append(marker);
$('.marker').draggable({
stop: function(event, ui) {
var thisNew = $(this);
var x = (ui.position.left / thisNew.parent().width()) * 100 + '%';
var y = (ui.position.top / thisNew.parent().height()) * 100 + '%';
thisNew.css('left', x);
thisNew.css('top', y);
console.log(x, y);
}
});
}
map.click(function (e) {
var x = e.offsetX/ $(this).width() * 100 + '%';
var y = e.offsetY/ $(this).height() * 100 + '%';
AddPoint(x, y, this);
});
});

Make several objects rotate on same orbit but with different positions using jQuery

Hello I was trying to find a solution for a small script that can rotate objects around 1 center but it seems a bit too tricky for me.
I've found almost perfect solution and tried to modify it to suit my needs but there's a problem.
I'm trying to make 3 objects with text to rotate with same speed, same orbit but different start positions as if they were apexes(vertices) of equilateral triangle.
Here's my fiddle so far:
( function ( $ ) {
jQuery.fn.orbit = function(s, options){
var settings = {
orbits: 1 // Number of times to go round the orbit e.g. 0.5 = half an orbit
,period: 3000 // Number of milliseconds to complete one orbit.
,maxfps: 25 // Maximum number of frames per second. Too small gives "flicker", too large uses lots of CPU power
,clockwise: false // Direction of rotation.
};
$.extend(settings, options); // Merge the supplied options with the default settings.
return(this.each(function(){
var p = $(this);
/* First obtain the respective positions */
var p_top = p.css('top' ),
p_left = p.css('left'),
s_top = s.css('top' ),
s_left = s.css('left');
/* Then get the positions of the centres of the objects */
var p_x = parseInt(p_top ) + p.height()/2,
p_y = parseInt(p_left) + p.width ()/2,
s_x = parseInt(s_top ) + s.height()/2,
s_y = parseInt(s_left) + s.width ()/2;
/* Find the Adjacent and Opposite sides of the right-angled triangle */
var a = s_x - p_x,
o = s_y - p_y;
/* Calculate the hypotenuse (radius) and the angle separating the objects */
var r = Math.sqrt(a*a + o*o);
var theta = Math.acos(a / r);
/* Calculate the number of iterations to call setTimeout(), the delay and the "delta" angle to add/subtract */
var niters = Math.ceil(Math.min(4 * r, settings.period, 0.001 * settings.period * settings.maxfps));
var delta = 2*Math.PI / niters;
var delay = settings.period / niters;
if (! settings.clockwise) {delta = -delta;}
niters *= settings.orbits;
/* create the "timeout_loop function to do the work */
var timeout_loop = function(s, r, theta, delta, iter, niters, delay, settings){
setTimeout(function(){
/* Calculate the new position for the orbiting element */
var w = theta + iter * delta;
var a = r * Math.cos(w);
var o = r * Math.sin(w);
var x = parseInt(s.css('left')) + (s.height()/2) - a;
var y = parseInt(s.css('top' )) + (s.width ()/2) - o;
/* Set the CSS properties "top" and "left" to move the object to its new position */
p.css({top: (y - p.height()/2),
left: (x - p.width ()/2)});
/* Call the timeout_loop function if we have not yet done all the iterations */
if (iter < (niters - 1)) timeout_loop(s, r, theta, delta, iter+1, niters, delay, settings);
}, delay);
};
/* Call the timeout_loop function */
timeout_loop(s, r, theta, delta, 0, niters, delay, settings);
}));
}
}) (jQuery);
$('#object1' ).orbit($('#center' ), {orbits: 2, period: 8000});
$('#object2' ).orbit($('#center' ), {orbits: 4, period: 4000});
$('#object3' ).orbit($('#center' ), {orbits: 8, period: 2000});
HTML:
<h1> Example</h1>
<div id='rotation'>
<div id='center' >C</div>
<div id='object1' >Text1</div>
<div id='object2' >Text2</div>
<div id='object3' >Text3</div>
</div>
CSS:
#rotation {position: relative; width: 600px; height: 600px; background-color: #898989}
#center {position: absolute; width: 20px; height: 20px;
top: 300px; left: 300px; background-color: #ffffff;
-moz-border-radius: 40px; border-radius: 40px;
text-align: center; line-height: 15px;
}
#object1 {position: absolute; width: 36px; height: 36px;
top: 300px; left: 200px; background-color: #ff8f23;
-moz-border-radius: 18px; border-radius: 18px;
text-align: center; line-height: 30px;
}
#object2 {position: absolute; width: 36px; height: 36px;
top: 300px; left: 200px; background-color: #ff8f23;
-moz-border-radius: 18px; border-radius: 18px;
text-align: center; line-height: 30px;
}
#object3 {position: absolute; width: 36px; height: 36px;
top: 300px; left: 200px; background-color: #ff8f23;
-moz-border-radius: 18px; border-radius: 18px;
text-align: center; line-height: 30px;
}
I used different speed and revolutions for each object because I can't figure out how to set different start positions without messing up. If I touch x,y coordinates of any object in CSS then the orbiting messes up. It seems that object positions calculations are connected. And if I change coordinates then the calculation also changes. But I can't figure out how to fix this.
If you change the starting position of each element and call the .orbit function on each of them passing the same arguments it should work.
Check this out: fiddle
This is a slightly modified version of your fiddle. (Changed the starting positions and the arguments when calling the .orbit function.
Correct me if I'm wrong or if I didn't answer your question.
Got it working as intended
Fiddle
Jquery code:
var txt = $('#text .txt'), txtLen = txt.size();
var deg2rad = function(a) { return a*Math.PI/180.0; }
var angle = 0, speed=0.1, delay = 0, r = 100;
(function rotate() {
for (var i=0; i<txtLen; i++) {
var a = angle - (i * 360 / txtLen);
$(txt[i]).css({top: r+(Math.sin(deg2rad(a))*r), left: r+(Math.cos(deg2rad(a))*r)});
}
angle = (angle - speed) % 360;
setTimeout(rotate, delay);
})();
var rotation = function (){
$("#image").rotate({
duration: 6000,
angle:360,
animateTo:0,
callback: rotation,
easing: function (x,t,b,c,d){
var d = 6000
return c*(t/d)+b;
}
});
}
rotation();
var txt2 = $('#text2 .txt2'), txt2Len = txt2.size();
var deg2rad = function(a) { return a*Math.PI/180.0; }
var angle = 13, speed=0.2, delay = 0, r = 100;
(function rotate() {
for (var i=0; i<txt2Len; i++) {
var a = angle - (i * 360 / txt2Len);
$(txt2[i]).css({top: r+(Math.sin(deg2rad(a))*r), left:
r+(Math.cos(deg2rad(a))*r)});
}
// increment our angle and use a modulo so we are always in the range [0..360] degrees
angle = (angle - speed) % 360;
// after a slight delay, call the exact same function again
setTimeout(rotate, delay);
})(); // invoke this boxed function to initiate the rotation
var rotation = function (){
$("#image").rotate({
duration: 6000,
angle:360,
animateTo:0,
callback: rotation,
easing: function (x,t,b,c,d){
var d = 6000
return c*(t/d)+b;
}
});
}
rotation();

Categories