I'm interested in how I can make a grid with an undetermined amount of columns and rows that I can put inside another div and have it not spill into others objects or mess with the parent size.
I want it to be square and I'm using Tailwind CSS but I can adapt to SCSS or vanilla CSS. Also I want it to be touchable/moveable with a mouse on desktop and touch capable devices.
How would I go about accomplishing this?
Assuming I've understood your question correctly, here is one way you could do it. I haven't tested it with a touch device but it shouldn't be hard to modify it to also respond to touch events.
const items = [
['a0', 'a1', 'a2'],
['b0', 'b1', 'b2'],
['c0', 'c1', 'c2']
];
let html = '';
for (let rowItems of items) {
html += '<div class="row">';
for (let item of rowItems) {
html += '<div class="item">';
html += item;
html += '</div>';
}
html += '</div>';
}
const viewElem = document.querySelector('#view');
const outputElem = document.querySelector('#output');
outputElem.innerHTML = html;
let mouseStartPos = null;
let startOffset = null;
outputElem.addEventListener('mousedown', e => {
outputElem.classList.remove('animate');
mouseStartPos = {
x: e.clientX,
y: e.clientY
};
startOffset = {
x: outputElem.offsetLeft - viewElem.offsetLeft,
y: outputElem.offsetTop - viewElem.offsetTop
};
});
window.addEventListener('mouseup', e => {
mouseStartPos = null;
startOffset = null;
outputElem.classList.add('animate');
const xGridOffset = -1 * Math.max(0, Math.min(Math.round((outputElem.offsetLeft - viewElem.offsetLeft) / -100), items.length - 1));
const yGridOffset = -1 * Math.max(0, Math.min(Math.round((outputElem.offsetTop - viewElem.offsetTop) / -100), items[0].length - 1));
outputElem.style.left = `${xGridOffset * 100}px`;
outputElem.style.top = `${yGridOffset * 100}px`;
});
window.addEventListener('mousemove', e => {
if (mouseStartPos) {
const xOffset = mouseStartPos.x - e.clientX;
const yOffset = mouseStartPos.y - e.clientY;
outputElem.style.left = `${-1 * xOffset + startOffset.x}px`;
outputElem.style.top = `${-1 * yOffset + startOffset.y}px`;
}
});
#view {
width: 100px;
height: 100px;
overflow: hidden;
border: 2px solid blue;
}
#output {
position: relative;
}
.row {
display: flex;
}
.item {
display: flex;
min-width: 100px;
width: 100px;
height: 100px;
box-sizing: border-box;
justify-content: center;
align-items: center;
border: 1px solid red;
}
#output.animate {
transition: left 1s ease 0s, top 1s ease 0s;
}
Drag it!<br/>
<br/>
<div id="view">
<div id="output"></div>
</div>
Related
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'm trying to make an image slider. But as you can see the distance between the first and last element is not consistent. If you keep on dragging to left, the distance decreases and if you keep on dragging to right, the distance increases. Looks like the code is behaving differently on different zoom levels (sometimes?) and hence distance between every elements is changing at times.
//project refers to placeholder rectangular divs
projectContainer = document.querySelector(".project-container")
projects = document.querySelectorAll(".project")
elementAOffset = projects[0].offsetLeft;
elementBOffset = projects[1].offsetLeft;
elementAWidth = parseInt(getComputedStyle(projects[0]).width)
margin = (elementBOffset - (elementAOffset + elementAWidth))
LeftSideBoundary = -(elementAWidth)
RightSideBoundary = (elementAWidth * (projects.length)) + (margin * (projects.length))
RightSidePosition = RightSideBoundary - elementAWidth;
initialPosition = 0; //referring to mouse
mouseIsDown = false
projectContainer.addEventListener("mousedown", e => {
mouseIsDown = true
initialPosition = e.clientX;
})
projectContainer.addEventListener("mouseup", e => {
mouseExit(e)
})
projectContainer.addEventListener("mouseleave", e => {
mouseExit(e);
})
function mouseExit(e) {
mouseIsDown = false
//updates translateX value of transform
projects.forEach(project => {
var style = window.getComputedStyle(project)
project.currentTranslationX = (new WebKitCSSMatrix(style.webkitTransform)).m41
project.style.transform = 'translateX(' + (project.currentTranslationX) + 'px)'
})
}
projectContainer.addEventListener("mousemove", e => {
if (!mouseIsDown) { return };
// adds mousemovement to translateX
projects.forEach(project => {
project.style.transform = 'translateX(' + ((project.currentTranslationX ?? 0) + (e.clientX - initialPosition)) + 'px)'
shiftPosition(e, project)
})
})
//teleports div if it hits left or right boundary to make an infinite loop
function shiftPosition(e, project) {
projectStyle = window.getComputedStyle(project)
projectTranslateX = (new WebKitCSSMatrix(projectStyle.webkitTransform)).m41
//projectVisualPosition is relative to the left border of container div
projectVisualPosition = project.offsetLeft + projectTranslateX
if (projectVisualPosition <= LeftSideBoundary) {
project.style.transform = "translateX(" + ((RightSidePosition - project.offsetLeft)) + "px)"
updateTranslateX(e);
}
if (projectVisualPosition >= RightSidePosition) {
newPosition = -1 * (project.offsetLeft + elementAWidth)
project.style.transform = "translateX(" + newPosition + "px)"
updateTranslateX(e);
}
}
function updateTranslateX(e) {
projects.forEach(project => {
style = window.getComputedStyle(project)
project.currentTranslationX = (new WebKitCSSMatrix(style.webkitTransform)).m41
project.style.transform = 'translateX(' + (project.currentTranslationX) + 'px)'
initialPosition = e.clientX
})
}
*, *::before, *::after{
margin:0px;
padding:0px;
box-sizing: border-box;
font-size:0px;
user-select: none;
}
.project-container{
font-size: 0px;
position: relative;
width:1500px;
height:400px;
background-color: rgb(15, 207, 224);
margin:auto;
margin-top:60px;
white-space: nowrap;
overflow: hidden;
padding-left:40px;
padding-right:40px;
}
.project{
font-size:100px;
margin:40px;
display: inline-block;
height:300px;
width:350px;
background-color:red;
border: black 3px solid;
user-select: none;
}
<div class="project-container">
<div class="project">1</div>
<div class="project">2</div>
<div class="project">3</div>
<div class="project">4</div>
<div class="project">5</div>
<div class="project">6</div>
<div class="project">7</div>
<div class="project">8</div>
</div>
I'm not sure exactly how you would go about fixing your implementation. I played around with it for a while and discovered a few things; dragging more quickly makes the displacement worse, and the displacement seems to happen mainly when the elements are teleported at each end of the container.
I would guess that the main reason for this is that you are looping over all the elements and spacing them individually. Mouse move events generally happen under 20ms apart, and you are relying on all the DOM elements being repainted with their new transform positions before the next move is registered.
I did come up with a different approach using absolutely placed elements and the IntersectionObserver API, which is now supported in all modern browsers. The idea here is basically that when each element intersects with the edge of the container, it triggers an array lookup to see if the next element in the sequence is on the correct end and moves it there if not. Elements are only ever spaced by a static variable, while the job of sliding them is passed up to a new parent wrapper .project-slider.
window.addEventListener('DOMContentLoaded', () => {
// Style variables
const styles = {
width: 350,
margin: 40
};
const space = styles.margin*2 + styles.width;
// Document variables
const projectContainer = document.querySelector(".project-container");
const projectSlider = document.querySelector(".project-slider");
const projects = Array.from(document.querySelectorAll(".project"));
// Mouse interactions
let dragActive = false;
let prevPos = 0;
projectContainer.addEventListener('mousedown', e => {
dragActive = true;
prevPos = e.clientX;
});
projectContainer.addEventListener('mouseup', () => dragActive = false);
projectContainer.addEventListener('mouseleave', () => dragActive = false);
projectContainer.addEventListener('mousemove', e => {
if (!dragActive) return;
const newTrans = projectSlider.currentTransX + e.clientX - prevPos;
projectSlider.style.transform = `translateX(${newTrans}px)`;
projectSlider.currentTransX = newTrans;
prevPos = e.clientX;
});
// Generate initial layout
function init() {
let workingLeft = styles.margin;
projects.forEach((project, i) => {
if (i === projects.length - 1) {
project.style.left = `-${space - styles.margin}px`;
} else {
i !== 0 && (workingLeft += space);
project.style.left = `${workingLeft}px`;
};
});
projectSlider.currentTransX = 0;
};
// Intersection observer
function observe() {
const callback = (entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// Find intersecting edge
const { left } = entry.boundingClientRect;
const isLeftEdge = left < projectContainer.clientWidth - left;
// Test and reposition next element
const targetIdx = projects.findIndex(project => project === entry.target);
let nextIdx = null;
const nextEl = () => projects[nextIdx];
const targetLeft = parseInt(entry.target.style.left);
const nextLeft = () => parseInt(nextEl().style.left);
if (isLeftEdge) {
nextIdx = targetIdx === 0 ? projects.length-1 : targetIdx - 1;
nextLeft() > targetLeft && (nextEl().style.left = `${targetLeft - space}px`);
} else {
nextIdx = targetIdx === projects.length-1 ? 0 : targetIdx + 1;
nextLeft() < targetLeft && (nextEl().style.left = `${targetLeft + space}px`);
};
};
});
};
const observer = new IntersectionObserver(callback, {root: projectContainer});
projects.forEach(project => observer.observe(project));
};
init();
observe();
});
*, *::before, *::after{
margin:0px;
padding:0px;
box-sizing: border-box;
font-size:0px;
user-select: none;
}
.project-container {
font-size: 0px;
width: 100%;
height: 400px;
background-color: rgb(15, 207, 224);
margin:auto;
margin-top:60px;
white-space: nowrap;
overflow: hidden;
}
.project-slider {
position: relative;
}
.project {
font-size:100px;
display: block;
position: absolute;
top: 40px;
height:300px;
width:350px;
background-color:red;
border: black 3px solid;
user-select: none;
}
<div class="project-container">
<div class="project-slider">
<div class="project">1</div>
<div class="project">2</div>
<div class="project">3</div>
<div class="project">4</div>
<div class="project">5</div>
<div class="project">6</div>
<div class="project">7</div>
<div class="project">8</div>
</div>
</div>
There is still an issue here which is how to resize the elements for smaller screens, and on browser resizes. You would have to add another event listener for window resizes which resets the positions and styles at certain breakpoints, and also determine the style variables programmatically when the page first loads. I believe this would still have been a partial issue with the original implementation so you'd have to address it at some point either way.
Why I cannot get value inside the loop
Can use javascript only
Pause text for 2000ms on hover
reverse work on hover but should pause it first and then reverse the sequence until mouse out.
loop on reading text from textarea "input"
find this line in javascript to control delay
if(document.getElementById('flag2').value == "0"){
/*
1 : center
2 - 4 : center 2
5 - 7 : center 3
8 - 10 : center 4
11 - 13 : center 5
14 - 16 : center 6
17 - 19 : center 7
*/
stopNow= false;
function Loop(txt, wpm) {
txt = txt.replace(/\n/g, ' ');
var a = txt.split(' '),
n = a.length,
i = 0,
b = document.getElementById('bw'),
tw = document.getElementById('tw'),
tx = {
L: document.getElementById('txt_L'),
C: document.getElementById('txt_C'),
R: document.getElementById('txt_R')
}
;
function width(w) {
tw.textContent = w;
return tw.offsetWidth;
}
function middle(w) {
var x = 0;
var n = w.length,
// Center char calculation. 1=1, 2-4=2, 5-7=3, ...
c = ~~((n + 1) / 3) + 1,
z = {};
if (!n)
return;
z.a = width(w.substr(0, c - 1));
z.b = width(w.substr(0, c));
z.c = (z.b - z.a) / 2;
b.style.paddingLeft = ~~(110 - (z.a + z.c)) + 'px';
tx.L.textContent = w.substr(0, c - 1);
tx.C.textContent = w.substr(c - 1, 1);
tx.R.textContent = w.substr(c);
}
function word() {
if(stopNow){
middle(a[i])
if (--i === n)
i = 0;
return 0;
}
else{
middle(a[i])
if (++i === n)
i = 0;
return 0;
}
}
this.whiteout = function(w) {
if (w) {
tx.L.style.color = '#fff';
tx.R.style.color = '#fff';
} else {
tx.L.style.color = '#080';
tx.R.style.color = '#000';
}
};
wpm = wpm || 200;
var __time = 2000;
//can not get changes value here
if(document.getElementById('flag2').value == "0"){
__time = (60/wpm) * 1000;}
document.getElementById('flag1').innerHTML += document.getElementById('flag2').innerHTML ;
var tt = setInterval(function (){
document.getElementById('flag1').innerHTML = word();
}, __time);
}
var txt = document.getElementById('input').value;
var ww = new Loop(txt, 250);
document.getElementById('bw').addEventListener('change', function() {
ww.whiteout(this.checked);
});
document.getElementById('txtHolder').addEventListener('mouseover', function () {
stopNow = true;
document.getElementById('flag2').innerHTML = "1";
});
document.getElementById('txtHolder').addEventListener('mouseout', function () {
stopNow = false;
document.getElementById('flag2').innerHTML = "0";
});
* {
padding: 0;
margin:0;
}
.hide {
display: none;
}
#tw {
position: absolute;
visibility: hidden;
white-space: nowrap;
}
#tw,
#txt_L,
#txt_C,
#txt_R
{
font: bold 18px Arial;
line-height: 1;
}
#txt_L { color: #000; }
#txt_C { color: #f00; }
#txt_R { color: #000; }
#wrap {
position: relative;
width: 220px;
height: 50px;
border-top: 1px solid #000;
border-bottom: 1px solid #000;
}
#b1 {
width: 110px;
height: 50px;
border-right: 1px solid #000;
float:left;
}
#bw {
position: absolute;
width: 220px;
top: 10px;
background: #fff;
padding: 5px 0;
line-height: 1;
vertical-align: middle;
}
<!--
EXTRA POINTS---------
Pause text for 2000ms on hover and reverse the sequence until mouse out.
-->
<div id="flag1">
</div>
<div id="flag2">
</div>
<div id="txtHolder">
<span id="tw"></span>
<div id="wrap">
<div id="b1"></div>
<div id="bw"><span id="txt_L"> </span><span id="txt_C"></span><span id="txt_R"></span></div>
</div>
</div>
<textarea id="input" class="hide">
Oh, hey there, TIDWRTWHUFOO fans. Thinking about going for a little dip this summer? How about you go for a swim with a one-ton crab that will smash you under its massive legs? Sound fun? Definitely!
Crabster is a wild crab-like robot designed to explore the deepest oceans without smashing itself into a squashed tin can. It has massive legs, a large body, and lots of cameras so it can find you even if you’re the dude from The Big Blue and can dive really deep. There’s no escaping our future robotic-aquatic overlords
</textarea>
There are two divs vertically having unequal height. The minimum height of each div can be 40px.
When i resize the browser window, I want the divs to resize maintaining the aspect ratio.
I have created a demo in JSBIN. It works but it gets into Maximum call stack.
function divideEqually(h1, h2, diff) {
let threshold = 40;
let diffSplit = diff / 2;
let leftOut1 = 0,
leftOut2 = 0,
leftOut = 0;
if (h1 != threshold) {
h1 = h1 + diffSplit;
} else {
leftOut1 = diffSplit;
}
if (h2 != threshold) {
h2 = h2 + diffSplit;
} else {
leftOut2 = diffSplit;
}
diff = 0;
if (h1 < threshold) {
leftOut1 = threshold - h1;
h1 = threshold;
}
if (h2 < threshold) {
leftOut2 = threshold - h2;
h2 = threshold;
}
diff = Math.ceil(leftOut1 + leftOut2);
// error margin
if (Math.abs(diff) > 0.5) {
return divideEqually(h1, h2, diff);
}
return {
h1: h1,
h2: h2
};
}
const qs = handle => document.querySelector(handle)
let initialHeight = window.innerHeight;
window.addEventListener('resize',()=>{
const newHeight = window.innerHeight;
const changeInHeight = newHeight - initialHeight;
const h1 = qs("#top");
const h2 = qs("#bottom")
const h = divideEqually(h1.clientHeight,h2.clientHeight,changeInHeight);
initialHeight = newHeight;
h1.style.height = h.h1 + "px";
h2.style.height = h.h2 + "px";
// debug
h1.innerHTML = h.h1;
h2.innerHTML = h.h2;
})
Is their any better way of doing this or optimising the function ?
No need for Javascript; modern browsers have got you covered. Just add the flex property to the divs' CSS. This example gives the divs a 65/35 split.
#container {
display: flex;
flex-direction: column;
height: 100vh;
}
#top {
flex: 65 0 0%;
min-height: 40px;
width: 200px;
background: red;
}
#bottom {
flex: 35 0 0%;
min-height: 40px;
width: 200px;
background: orange;
}
<div id="container">
<div id="top"></div>
<div id="bottom"></div>
</div>