I am trying to transform this element into a standard web component using Lit. (https://www.w3schools.com/howto/howto_js_image_comparison.asp)
I totally new to Lit and to web components and am struggling to select elements from the shadow DOM. Right now, I am stuck with the var x inside the initComparisons() function. I am aware that the document object does not exist in the shadow dom and must be replaced by renderRoot, however, I am not sure either If I am selecting the elements the right way or what does replace the window object... Do you notice something wrong with this code? I am stuck at the first lines of the initComparisons() function as x always returns null no matter what I do....
Any help will be appreciated.
Thank you very much.
import {
LitElement,
css,
html,
} from "https://cdn.jsdelivr.net/gh/lit/dist#2/all/lit-all.min.js";
export class Comparator extends LitElement {
static properties = {
baseImage: "",
imageWidth: "",
imageHeight: "",
altImage: "",
};
// Define scoped styles right with your component, in plain CSS
static styles = css`
* {
box-sizing: border-box;
}
.img-comp-container {
position: relative;
height: 200px; /*should be the same height as the images*/
}
.img-comp-img {
position: absolute;
width: auto;
height: auto;
overflow: hidden;
}
.img-comp-img img {
display: block;
vertical-align: middle;
}
.img-comp-slider {
position: absolute;
z-index: 11;
cursor: ew-resize;
/*set the appearance of the slider:*/
width: 40px;
height: 40px;
background-color: #2196f3;
opacity: 0.7;
border-radius: 50%;
}
.border-slider {
position: absolute;
z-index: 10;
cursor: ew-resize;
/*set the appearance of the slider:*/
width: 5px;
height: 130%;
background-color: red;
opacity: 1;
}
.border-slider::after {
content: url("./separator.svg");
position: absolute;
width: 30px;
height: 30px;
background: red;
top: calc(50% - 15px);
left: calc(50% - 15px);
}
`;
constructor() {
super();
// Declare reactive defaults
this.baseImage = "https://api.lorem.space/image/house?w=800&h=600";
this.altImage = "https://api.lorem.space/image/house?w=800&h=600";
this.imageWidth = "800px";
this.imageHeight = "600px";
}
connectedCallback() {
super.connectedCallback();
this.initComparisons();
}
// Render the UI as a function of component state
render() {
return html`
<div class="img-comp-container">
<div class="img-comp-img">
<img
src="${this.baseImage}"
width="${this.imageWidth}"
height="${this.imageHeight}"
/>
</div>
<div id="img-comp-overlay" class="img-comp-img">
<img
src="${this.altImage}"
width="${this.imageWidth}"
height="${this.imageHeight}"
/>
</div>
</div>
`;
}
//HELPER FUCTIONS GO HERE
initComparisons() {
var x, i;
/*find all elements with an "overlay" class:*/
x = this.renderRoot.querySelector("#img-comp-overlay");
for (i = 0; i < x.length; i++) {
/*once for each "overlay" element:
pass the "overlay" element as a parameter when executing the compareImages function:*/
compareImages(x[i]);
}
function compareImages(img) {
var slider,
img,
clicked = 0,
w,
h;
/*get the width and height of the img element*/
w = img.offsetWidth;
h = img.offsetHeight;
/*set the width of the img element to 50%:*/
img.style.width = w / 2 + "px";
/*create slider:*/
slider = this.renderRoot.createElement("DIV");
slider.setAttribute("class", "border-slider");
/*insert slider*/
img.parentElement.insertBefore(slider, img);
/*position the slider in the middle:*/
slider.style.top = h / 2 - slider.offsetHeight / 2 + "px";
slider.style.left = w / 2 - slider.offsetWidth / 2 + "px";
/*execute a function when the mouse button is pressed:*/
slider.addEventListener("mousedown", slideReady);
/*and another function when the mouse button is released:*/
this.renderRoot.addEventListener("mouseup", slideFinish);
/*or touched (for touch screens:*/
slider.addEventListener("touchstart", slideReady);
/*and released (for touch screens:*/
window.addEventListener("touchend", slideFinish);
function slideReady(e) {
/*prevent any other actions that may occur when moving over the image:*/
e.preventDefault();
/*the slider is now clicked and ready to move:*/
clicked = 1;
/*execute a function when the slider is moved:*/
window.addEventListener("mousemove", slideMove);
window.addEventListener("touchmove", slideMove);
}
function slideFinish() {
/*the slider is no longer clicked:*/
clicked = 0;
}
function slideMove(e) {
var pos;
/*if the slider is no longer clicked, exit this function:*/
if (clicked == 0) return false;
/*get the cursor's x position:*/
pos = getCursorPos(e);
/*prevent the slider from being positioned outside the image:*/
if (pos < 0) pos = 0;
if (pos > w) pos = w;
/*execute a function that will resize the overlay image according to the cursor:*/
slide(pos);
}
function getCursorPos(e) {
var a,
x = 0;
e = e.changedTouches ? e.changedTouches[0] : e;
/*get the x positions of the image:*/
a = img.getBoundingClientRect();
/*calculate the cursor's x coordinate, relative to the image:*/
x = e.pageX - a.left;
/*consider any page scrolling:*/
x = x - window.pageXOffset;
return x;
}
function slide(x) {
/*resize the image:*/
img.style.width = x + "px";
/*position the slider:*/
slider.style.left = img.offsetWidth - slider.offsetWidth / 2 + "px";
}
}
}
}
customElements.define("image-compare", Comparator);
connectedCallback() is likely too early to call this.initComparison() as the elements might not have been populated within the shadowroot. That happens on first render so you can grab those elements in firstUpdated() instead.
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.
I have a large background image and some much smaller images for the user to drag around on the background. I need this to be efficient in terms of performance, so i'm trying to avoid libraries. I'm fine with drag 'n' drop if it work's well, but im trying to get drag.
Im pretty much trying to do this. But after 8 years there must be a cleaner way to do this right?
I currently have a drag 'n' drop system that almost works, but when i drop the smaller images, they are just a little off and it's very annoying. Is there a way to fix my code, or do i need to take a whole different approach?
This is my code so far:
var draggedPoint;
function dragStart(event) {
draggedPoint = event.target; // my global var
}
function drop(event) {
event.preventDefault();
let xDiff = draggedPoint.x - event.pageX;
let yDiff = draggedPoint.y - event.pageY;
let left = draggedPoint.style.marginLeft; // get margins
let top = draggedPoint.style.marginTop;
let leftNum = Number(left.substring(0, left.length - 2)); // cut off px from the end
let topNum = Number(top.substring(0, top.length - 2));
let newLeft = leftNum - xDiff + "px" // count new margins and put px back to the end
let newTop = topNum - yDiff + "px"
draggedPoint.style.marginLeft = newLeft;
draggedPoint.style.marginTop = newTop;
}
function allowDrop(event) {
event.preventDefault();
}
let imgs = [
"https://upload.wikimedia.org/wikipedia/commons/6/67/Orange_juice_1_edit1.jpg",
"https://upload.wikimedia.org/wikipedia/commons/f/ff/Solid_blue.svg",
"https://upload.wikimedia.org/wikipedia/commons/b/b4/Litoria_infrafrenata_-_Julatten.jpg"
]
/* my smaller images: */
for (let i = 0; i < 6; i++) {
let sensor = document.createElement("img");
sensor.src = imgs[i % imgs.length];
sensor.alt = i;
sensor.draggable = true;
sensor.classList.add("sensor");
sensor.style.marginLeft = `${Math.floor(Math.random() * 900)}px`
sensor.style.marginTop = `${Math.floor(Math.random() * 500)}px`
sensor.onclick = function() {
sensorClick(logs[i].id)
};
sensor.addEventListener("dragstart", dragStart, null);
let parent = document.getElementsByClassName("map")[0];
parent.appendChild(sensor);
}
<!-- my html: -->
<style>
.map {
width: 900px;
height: 500px;
align-content: center;
margin: 150px auto 150px auto;
}
.map .base {
position: absolute;
width: inherit;
height: inherit;
}
.map .sensor {
position: absolute;
width: 50px;
height: 50px;
}
</style>
<div class="map" onDrop="drop(event)" ondragover="allowDrop(event)">
<img src='https://upload.wikimedia.org/wikipedia/commons/f/f7/Plan-Oum-el-Awamid.jpg' alt="pohja" class="base" draggable="false">
<div>
With the answers from here and some time i was able to get a smooth drag and click with pure js.
Here is a JSFiddle to see it in action.
let maxLeft;
let maxTop;
const minLeft = 0;
const minTop = 0;
let timeDelta;
let imgs = [
"https://upload.wikimedia.org/wikipedia/commons/6/67/Orange_juice_1_edit1.jpg",
"https://upload.wikimedia.org/wikipedia/commons/f/ff/Solid_blue.svg",
"https://upload.wikimedia.org/wikipedia/commons/b/b4/Litoria_infrafrenata_-_Julatten.jpg"
]
var originalX;
var originalY;
window.onload = function() {
document.onmousedown = startDrag;
document.onmouseup = stopDrag;
}
function sensorClick () {
if (Date.now() - timeDelta < 150) { // check that we didn't drag
createPopup(this);
}
}
// create a popup when we click
function createPopup(parent) {
let p = document.getElementById("popup");
if (p) {
p.parentNode.removeChild(p);
}
let popup = document.createElement("div");
popup.id = "popup";
popup.className = "popup";
popup.style.top = parent.y - 110 + "px";
popup.style.left = parent.x - 75 + "px";
let text = document.createElement("span");
text.textContent = parent.id;
popup.appendChild(text);
var map = document.getElementsByClassName("map")[0];
map.appendChild(popup);
}
// when our base is loaded
function baseOnLoad() {
var map = document.getElementsByClassName("map")[0];
let base = document.getElementsByClassName("base")[0];
maxLeft = base.width - 50;
maxTop = base.height - 50;
/* my smaller images: */
for (let i = 0; i < 6; i++) {
let sensor = document.createElement("img");
sensor.src = imgs[i % imgs.length];
sensor.alt = i;
sensor.id = i;
sensor.draggable = true;
sensor.classList.add("sensor");
sensor.classList.add("dragme");
sensor.style.left = `${Math.floor(Math.random() * 900)}px`
sensor.style.top = `${Math.floor(Math.random() * 500)}px`
sensor.onclick = sensorClick;
let parent = document.getElementsByClassName("map")[0];
parent.appendChild(sensor);
}
}
function startDrag(e) {
timeDelta = Date.now(); // get current millis
// determine event object
if (!e) var e = window.event;
// prevent default event
if(e.preventDefault) e.preventDefault();
// IE uses srcElement, others use target
targ = e.target ? e.target : e.srcElement;
originalX = targ.style.left;
originalY = targ.style.top;
// check that this is a draggable element
if (!targ.classList.contains('dragme')) return;
// calculate event X, Y coordinates
offsetX = e.clientX;
offsetY = e.clientY;
// calculate integer values for top and left properties
coordX = parseInt(targ.style.left);
coordY = parseInt(targ.style.top);
drag = true;
document.onmousemove = dragDiv; // move div element
return false; // prevent default event
}
function dragDiv(e) {
if (!drag) return;
if (!e) var e = window.event;
// move div element and check for borders
let newLeft = coordX + e.clientX - offsetX;
if (newLeft < maxLeft && newLeft > minLeft) targ.style.left = newLeft + 'px'
let newTop = coordY + e.clientY - offsetY;
if (newTop < maxTop && newTop > minTop) targ.style.top = newTop + 'px'
return false; // prevent default event
}
function stopDrag() {
if (typeof drag == "undefined") return;
if (drag) {
if (Date.now() - timeDelta > 150) { // we dragged
let p = document.getElementById("popup");
if (p) {
p.parentNode.removeChild(p);
}
} else {
targ.style.left = originalX;
targ.style.top = originalY;
}
}
drag = false;
}
.map {
width: 900px;
height: 500px;
margin: 50px
position: relative;
}
.map .base {
position: absolute;
width: inherit;
height: inherit;
}
.map .sensor {
display: inline-block;
position: absolute;
width: 50px;
height: 50px;
}
.dragme {
cursor: move;
left: 0px;
top: 0px;
}
.popup {
position: absolute;
display: inline-block;
width: 200px;
height: 100px;
background-color: #9FC990;
border-radius: 10%;
}
.popup::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -10px;
border-width: 10px;
border-style: solid;
border-color: #9FC990 transparent transparent transparent;
}
.popup span {
width: 90%;
margin: 10px;
display: inline-block;
text-align: center;
}
<div class="map" width="950px" height="500px">
<img src='https://upload.wikimedia.org/wikipedia/commons/f/f7/Plan-Oum-el-Awamid.jpg' alt="pohja" class="base" draggable="false" onload="baseOnLoad()">
<div>
I have this for my js and it returns a modified version of the distance between my element and my cursor... I want to use it to scale a separate element, any ideas as to how I would do that?
var elm = document.getElementById("qrcode");
elm.addEventListener("mousemove",getcordd , false)
function getcordd(ev){
var bdns = ev.target.getBoundingClientRect();
var y = ev.clientY - bdns.left;
var x = ev.clientX - bdns.top;
var d = Math.sqrt(x*x+y*y);
var s = (d*0.1)
console.log (`${s}`);
}
This is my CSS
.cursor {
position: absolute;
width: 250px;
height: 250px;
top: -50%;
left: -50%;
margin: -125px 0 0 -125px;
border-radius: 50%;
background-color: white;
transition: transform 0.8s ease-out;
mix-blend-mode: difference;
filter: grayscale(2);
pointer-events: none;
}
.cursor.is-moving {
transform: scale (var(`${s}`));
}
This is my HTML:
<div class="cursor"></div>
<main>
<h1>Find the QR code for the spotify playlist</h1>
<div id="qrcode">
<img id="QRCode" src="img/qr-code.png" alt="">
</div>
</main>
I basically made a custom curser that I would like to scale as I get closer to the element which is a QRCode
The goal would be to make it so the .cursor class scales up to 1 as it gets closer to the #qrcode. And gets smaller as it goes further away. I would like it to be relative to the window...
I am still very new to coding so I am not sure about how to do it and I have not found any information on the internet
If I correctly understand, you could do it like this
Keep in mind, that elm.addEventListener("mousemove"... would trigger function only if your cursor is already moves over elm (#qrcode), because you attach event listener to elm, not to whole window.
var elm = document.getElementById("qrcode");
var cursor = document.querySelector('.cursor');
elm.addEventListener("mousemove", function(ev) {
var distance = getcordd(ev);
cursor.style=`transform: scale(${s})`;
}, false);
function getcordd(ev) {
var bdns = ev.target.getBoundingClientRect();
var y = ev.clientY - bdns.left;
var x = ev.clientX - bdns.top;
var d = Math.sqrt(x * x - y * y);
var s = d * 0.1;
return s;
}
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>