overflow-anchor doesn't work for horizontal scrolling - javascript

I would like to build an infinite horizontal scroll that scrolls in both directions - left and right. As user scrolls to the left, new content is prepended to the scrollable element (think scrolling through a schedule history, for example). As they scroll to the right, content is appended.
I have learned that browsers anchor content when scrolling up and down which is fantastic, exactly what I'd expect. The effect of that is that prepending content to the scrolled element anchors user to their current, logical position and the content doesn't "jump".
But the anchoring doesn't seem to work when scrolling left or right. The behaviour is as if I set overflow-anchor: none. What can I do to make it work as well as when scrolling up?
let topCounter = 0;
document.querySelector('.scrollable-top').scrollTo({ top: 100 });
document.querySelector('.scrollable-top').onscroll = (event) => {
if (event.target.scrollTop < 100) {
let box = document.createElement('div');
box.className = 'content-box';
box.textContent = `${topCounter--}`;
document.querySelector('.scrollable-top').prepend(box);
}
};
let leftCounter = 0;
document.querySelector('.scrollable-left').scrollTo({ left: 100 });
document.querySelector('.scrollable-left').onscroll = (event) => {
if (event.target.scrollLeft < 100) {
let box = document.createElement('div');
box.className = 'content-box';
box.textContent = `${leftCounter--}`;
document.querySelector('.scrollable-left').prepend(box);
}
};
.scrollable-top {
width: 200px;
height: 250px;
overflow-y: auto;
border: solid 1px black;
}
.scrollable-left {
display: flex;
width: 250px;
overflow-x: auto;
border: solid 1px black;
}
.content-box {
flex: 1 0 auto;
height: 150px;
width: 150px;
border: solid 1px red;
display: flex;
justify-content: center;
align-items: center;
}
<div class="scrollable-top">
<div class="content-box">1</div>
<div class="content-box">2</div>
<div class="content-box">3</div>
</div>
<div class="scrollable-left">
<div class="content-box">1</div>
<div class="content-box">2</div>
<div class="content-box">3</div>
</div>

Scroll the horizontal container to the right by 150 using scrollBy(150, 0):
let topCounter = 0;
document.querySelector('.scrollable-top').scrollTo({ top: 100 });
document.querySelector('.scrollable-top').onscroll = (event) => {
if (event.target.scrollTop < 100) {
let box = document.createElement('div');
box.className = 'content-box';
box.textContent = `${topCounter--}`;
document.querySelector('.scrollable-top').prepend(box);
}
};
let leftCounter = 0;
document.querySelector('.scrollable-left').scrollTo({ left: 100 });
document.querySelector('.scrollable-left').onscroll = (event) => {
if (event.target.scrollLeft < 100) {
let box = document.createElement('div');
box.className = 'content-box';
box.textContent = `${leftCounter--}`;
document.querySelector('.scrollable-left').prepend(box);
// solution ------------------
event.target.scrollBy(150, 0);
}
};
.scrollable-top {
width: 200px;
height: 250px;
overflow-y: auto;
border: solid 1px black;
display: inline-block;
float:left;
}
.scrollable-left {
display: flex;
width: 250px;
overflow-x: auto;
border: solid 1px black;
float:right;
}
.content-box {
flex: 1 0 auto;
height: 150px;
width: 150px;
border: solid 1px red;
display: flex;
justify-content: center;
align-items: center;
}
<div class="scrollable-top">
<div class="content-box">1</div>
<div class="content-box">2</div>
<div class="content-box">3</div>
</div>
<div class="scrollable-left">
<div class="content-box">1</div>
<div class="content-box">2</div>
<div class="content-box">3</div>
</div>
As per the specs, following is the intent behind such anchoring:
Changes in DOM elements above the visible region of a scrolling box can result in the page moving while the user is in the middle of consuming the content.
This spec proposes a mechanism to mitigate this jarring user experience
by keeping track of the position of an anchor node and adjusting the scroll offset accordingly.
This spec also proposes an API for web developers to opt-out of this behavior.
Since no page loads horizontally, I think they didn't implement this for horizontal scrollbars. Also, apart from above use case it makes no sense to implement this behavior.
Note: Safari doesn't implement the overflow-anchor behavior. So, your code for vertical scroll fails in Safari.
I've tried my code, for horizontal scrolling, on Safari and it works. So incase you want to implement infinite vertical scroll, and want to support all the browsers, then you'll have to optout of overflow-anchor behavior and use scrollBy(x,y) to do it manually. :(

I tried to fix your code and got this option
let leftCounter = -2;
let rightCounter = 2;
document.querySelector('.scrollable-left').scrollTo({ left: 100 });
document.querySelector('.scrollable-left').onscroll = (event) => {
if (event.target.scrollLeft < 100) {
let box = document.createElement('div');
box.className = 'content-box';
box.textContent = `${leftCounter--}`;
document.querySelector('.scrollable-left').prepend(box);
event.target.scrollLeft += 250
}
if ((event.target.scrollWidth - event.target.scrollLeft - 250) < 100) {
let box = document.createElement('div');
box.className = 'content-box';
box.textContent = `${rightCounter++}`;
document.querySelector('.scrollable-left').append(box);
}
};
.scrollable-top {
width: 200px;
height: 250px;
overflow-y: auto;
border: solid 1px black;
}
.scrollable-left {
display: flex;
width: 250px;
overflow-x: auto;
border: solid 1px black;
}
.content-box {
flex: 1 0 auto;
height: 150px;
width: 150px;
border: solid 1px red;
display: flex;
justify-content: center;
align-items: center;
}
<div class="scrollable-left">
<div class="content-box">-1</div>
<div class="content-box">0</div>
<div class="content-box">1</div>
</div>

Related

Make both overlapping divs clickable?

Is it possible to make two overlapping divs, both clickable?
I've appended divs to two containers, #container and #container2. Their styles are exactly the same only except one is flex-direction: column; and one is flex-direction: column;. Both position:absolute with #container2 on top. I made each of the appended child clickable to fill its background color. Only the div on top is clickable so far, is there a way to make both clickable? or is there another way to have the bottom div react to my clicks?
window.addEventListener('load', init);
function init() {
calculateGrid();
//calculate grid
function calculateGrid() {
var w = window.innerWidth;
var h = window.innerHeight;
var totalNum = Math.trunc(w / 25) * Math.trunc(h / 25);
function randomInRange(from, to) {
let x = Math.random() * (to - from);
return x + from;
};
for (var i = 0; i < totalNum; i++) {
var div = document.createElement('div');
div.setAttribute('class', 'grid');
div.style.width = randomInRange(3, 10) + 'vw';
div.style.height = randomInRange(5, 10) + 'vh';
document.getElementById('container').appendChild(div);
document.getElementById('container2').appendChild(div.cloneNode(true));
}
};
$(".grid").click(function() {
$(this).toggleClass('selected');
});
};
#container {
width: 100vw;
height: 95vh;
position: absolute;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
flex-direction: column;
overflow: hidden;
}
#container .grid {
border: 1px solid blue;
}
#container2 {
width: 100vw;
height: 95vh;
position: absolute;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
flex-direction: row;
overflow: hidden;
}
#container2 .grid {
border: 1px solid red;
}
.grid {
font-size: 10px;
color: white;
}
#container .selected {
background-color: blue;
}
#container2 .selected {
background-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<div id="wrapper">
<div id="container"></div>
<div id="container2"></div>
</div>
View on CodePen
One method is to use Document.elementsFromPoint() to return "an array of all elements at the specified coordinates". Iterate through that array, adding the "selected" class to "grid" elements.
window.addEventListener('load', init);
function init() {
// build grid
function calculateGrid() {
var w = window.innerWidth;
var h = window.innerHeight;
var totalNum = Math.trunc(w / 25) * Math.trunc(h / 25);
function randomInRange(from, to) {
let x = Math.random() * (to - from);
return x + from;
};
for (var i = 0; i < totalNum; i++) {
var div = document.createElement('div');
div.setAttribute('class', 'grid');
div.style.width = randomInRange(3, 10) + 'vw';
div.style.height = randomInRange(5, 10) + 'vh';
document.getElementById('container1').appendChild(div);
document.getElementById('container2').appendChild(div.cloneNode(true));
}
};
// handle grid clicks
function handleGridClick(e) {
let elms = document.elementsFromPoint(e.clientX, e.clientY);
Array.from(elms).forEach(elm => {
if (elm.classList.contains('grid'))
elm.classList.add('selected');
});
}
// initialize grid and click handler
calculateGrid();
document.addEventListener('click', handleGridClick);
};
.container {
width: 100vw;
height: 95vh;
position: absolute;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
overflow: hidden;
}
#container1 {
flex-direction: column;
}
#container1 .grid {
border: 1px solid blue;
}
#container1 .grid.selected {
background-color: blue;
}
#container2 .grid {
border: 1px solid red;
}
#container2 .grid.selected {
background-color: red;
}
<div id="wrapper">
<div id="container1" class="container"></div>
<div id="container2" class="container"></div>
</div>
You can't actually hover two items at the same time in plain 'ol HTML/CSS - for that you will need JavaScript as explained in the accepted solution. However, there's a CSS-only solution to allow hovering over the different layers, which was fun to figure out at the very least.
So the idea is that you have these invisible boxes on top of the visible ones. The invisible boxes only have borders such that any time your mouse hits a border, some clever z-index swapping takes place to make the visible containers change their stacking order.
For every .grid item you need to create a corresponding .box item: https://jsfiddle.net/ryanwheale/01v5yz86/93/

How to make a div resizer with pure JAvaScript

I am trying to create a div resizer and due to some restrictions, I cannot use jQuery and I am forced to use pure JavaScript for that. In the current state, it works, but it breaks if the div that will have the slider does not have the position set to absolute. Is there a way to solve that issue? Thank you very much.
I am a student learning to write JavaScript/CSS/HTML code and so I am relatively new to this.
const BORDER_SIZE = 4;
const panel = document.getElementById("left_panel");
const StationaryPanel = document.getElementById("right_panel");
const parent = document.getElementById("parent");
const label1 = document.getElementById("lb1");
const label2 = document.getElementById("lb2");
const label3 = document.getElementById("lb3");
let m_pos;
function resize(e) {
const dx = m_pos - e.x;
m_pos = e.x;
lb1.innerHTML = panel.offsetWidth;
lb2.innerHTML = StationaryPanel.offsetWidth;
lb3.innerHTML = parent.offsetWidth;
//lb3.innerHTML = document.body.clientWidth;
panel.style.width = (parseInt(getComputedStyle(panel, '').width) + dx) + "px";
StationaryPanel.style.width = (parent.offsetWidth - panel.offsetWidth) + "px";
//StationaryPanel.style.width = (document.body.clientWidth - panel.offsetWidth) + "px";
}
panel.addEventListener("mousedown", function(e) {
if (e.offsetX < BORDER_SIZE) {
m_pos = e.x;
if (panel.style.width < panel.minWidth || panel.style.width > panel.maxWidth) {
return;
}
document.addEventListener("mousemove", resize, false);
}
}, false);
document.addEventListener("mouseup", function() {
document.removeEventListener("mousemove", resize, false);
}, false);
#left_panel {
position: absolute;
width: 96px;
min-width: 50px;
max-width: 500px;
padding-left: 4px;
height: 100%;
right: 0;
background-color: #f0f0ff;
}
#left_panel::before {
content: " ";
background-color: #ccc;
position: absolute;
left: 0;
width: 4px;
height: 100%;
cursor: w-resize;
}
#right_panel {
width: 1000px;
height: 100%;
background-color: #ff0000;
}
#parent {
width: 800px;
}
<body>
<div id="parent">
<div id="left_panel">
This is the left div, the one that moves
</div>
<div id="right_panel">
This is the right div, the one that stays the same
</div>
</div>
<p id="lb1"></p>
<p>This is the left panel width ^</p>
<p id="lb2"></p>
<p>This is the right panel width ^</p>
<p id="lb3"></p>
<p>This is the parent width ^</p>
</body>
Here is the simplest way you can do it using CSS if that is not an issue here no need to use JS for this feature, it is just an example but it definitely help you on your way.
#MainDiv {
display: flex;
flex-direction: row;
height: 200px;
width: 400px;
border: 1px solid red;
position: relative;
}
#leftDiv {
width: 200px;
border: 1px solid yellow;
}
#rightDiv {
border: 2px solid;
padding: 20px;
width: 300px;
/* you can make this vertical/auto to make resize both ways */
resize: horizontal;
overflow: auto;
}
<div id="MainDiv">
<div id="leftDiv">
Left Div
</div>
<div id="rightDiv">
Right Div
</div>
</div>
Here is it in Javascript:
var h = $('#handle'),
l = $('#left'),
r = $('#right'),
w = $('body').width() - 18;
var isDragging = false;
h.mousedown(function(e) {
isDragging = true;
e.preventDefault();
});
$(document).mouseup(function() {
isDragging = false;
}).mousemove(function(e) {
if (isDragging) {
l.css('width', e.pageX);
r.css('width', w - e.pageX);
}
});
#left,
#right {
border: 1px solid #aaa;
float: left;
height: 100px;
width: 48%;
}
#handle {
background: #000;
float: left;
height: 100px;
margin: 1px;
/* Slider width */
width: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="left"> Left</div>
<div id="handle"></div>
<div id="right">Right</div>

How do i center two divs vertically in Javascript?

Here is the code snippet:
var wrapper = document.createElement('DIV');
wrapper.setAttribute("width", x * rows);
wrapper.setAttribute("height", y * columns);
wrapper.align = "center";
var buttonWrap = document.createElement('DIV');
buttonWrap.setAttribute("style", "clear:float");
As you can see in my code snippet, I have tried to center my div. But this code doesn't work. What works is making both divs fixed. But at the end of the day, the second div will then be upon the first div.
Please help.
If you can use only CSS I would do it this way:
.container {
width: 500px;
height: 150px;
border: solid black 1px;
/* Align center */
display: flex;
justify-content: center;
align-items: center;
}
.small {
background-color: red;
width: 100px;
height: 30px;
}
.big {
background-color: green;
width: 100px;
height: 100px;
}
<div class="container">
<div class="small"></div>
<div class="big"></div>
</div>
If you want to do it in javascript, apply the style written above in CSS this way:
var container = document.getElementsByClassName('container')[0];
container.style.display = "flex";
and so on...

How can I get this flexbox to expand with it's flex items? [duplicate]

This question already has answers here:
Flex elements ignore percent padding in Firefox
(4 answers)
Closed 4 years ago.
In my example code, please click on the Generate Content button in order to understand the issue.
Once you click on the button, you can see all of the flex items(.each-result) generate. They are almost completely wrapped by the div/flexbox (.result-container), indicated by the blue dotted border. If I remove the margins from flex-items, it fits perfectly into the div. However, when I add the margins, the parent div (ie. the flexbox) doesn't expand to it's full width; it remains the same width as when there was no margin.
Is there anyway to change this so that the div expands when adding margin?
const leftArrow = document.querySelector('#left-arrow');
const rightArrow = document.querySelector('#right-arrow');
const rootDiv = document.querySelector('#root');
const generateButton = document.querySelector("#button-generate");
var navMargin = '';
let rootContainerWidth = window.getComputedStyle(rootDiv, null).getPropertyValue("width");
console.log(`Window size onload: ${rootContainerWidth}`);
window.addEventListener('resize', () => {
rootContainerWidth = window.getComputedStyle(rootDiv, null).getPropertyValue("width");
console.log(`The new window size is ${rootContainerWidth}`);
})
//This code basically generates the content within the div
generateButton.addEventListener("click", () => {
for (let i = 0; i < 10; i++) {
const newDiv = document.createElement("div");
newDiv.classList.add("each-result");
newDiv.appendChild(addImg("https://uk.usembassy.gov/wp-content/uploads/sites/16/please_read_icon_150x150.jpg"));
rootDiv.appendChild(newDiv);
}
rootDiv.firstElementChild.classList.add('nav-margin');
navMargin = document.querySelector('.nav-margin');
});
//These enable the arrow to scroll through the dynamically generated content
// function navArrow () {
// leftArrow.addEventListener('click', () => {
// });
// rightArrow.addEventListener('click', () => {
// if ()
// });
// }
//Simple function to create and image element with the src attribute set in one line
function addImg(url) {
const newImg = document.createElement("img");
newImg.setAttribute("src", url);
return newImg;
}
html, body {
height: 100%;
}
button {
position: relative;
z-index: 1
width: auto;
height: 50px;
}
.container {
display: flex;
justify-content: center;
position: relative;
top: 15%;
z-index: 0
}
.each-result {
height: 150px;
width: 150px;
border: 3px dotted red;
margin: 0 1%;
}
img {
height: 100%;
width: auto;
}
.nav-arrows {
display: flex;
justify-content: space-between;
width: 100%;
height: auto;
position: absolute;
background: clear;
pointer-events: none;
}
#left-arrow, #right-arrow {
pointer-events: auto;
}
#root-container {
display: flex;
align-items: center;
border: 1px solid black;
height: 200px;
position: relative;
flex-flow: row no-wrap;
/* overflow: hidden; */
width: 100%;
}
.result-container {
display: flex;
border: 2px blue dotted;
}
<script src="https://use.fontawesome.com/releases/v5.0.6/js/all.js"></script>
<div class="container">
<div class="nav-arrows">
<button id="left-arrow"><i class="fas fa-arrow-alt-circle-left"></i>
</button>
<button id="right-arrow"> <i class="fas fa-arrow-alt-circle-right"></i>
</button>
</div>
<div id="root-container">
<div id="root" class="result-container">
</div>
</div>
</div>
<button id="button-generate">Generate Content</button>
If the margin can be a fixed value (instead of a percent), we can calc() the width of the element to account for the margin. For example, if we wanted a margin of 20px we'd do the following on the .each-result elements:
.each-result {
width: calc(10% + 20px);
margin: 0 20px;
}
Here's the working demo:
const leftArrow = document.querySelector('#left-arrow');
const rightArrow = document.querySelector('#right-arrow');
const rootDiv = document.querySelector('#root');
const generateButton = document.querySelector("#button-generate");
var navMargin = '';
let rootContainerWidth = window.getComputedStyle(rootDiv, null).getPropertyValue("width");
console.log(`Window size onload: ${rootContainerWidth}`);
window.addEventListener('resize', () => {
rootContainerWidth = window.getComputedStyle(rootDiv, null).getPropertyValue("width");
console.log(`The new window size is ${rootContainerWidth}`);
})
//This code basically generates the content within the div
generateButton.addEventListener("click", () => {
for (let i = 0; i < 10; i++) {
const newDiv = document.createElement("div");
newDiv.classList.add("each-result");
newDiv.appendChild(addImg("https://uk.usembassy.gov/wp-content/uploads/sites/16/please_read_icon_150x150.jpg"));
rootDiv.appendChild(newDiv);
}
rootDiv.firstElementChild.classList.add('nav-margin');
navMargin = document.querySelector('.nav-margin');
});
//These enable the arrow to scroll through the dynamically generated content
// function navArrow () {
// leftArrow.addEventListener('click', () => {
// });
// rightArrow.addEventListener('click', () => {
// if ()
// });
// }
//Simple function to create and image element with the src attribute set in one line
function addImg(url) {
const newImg = document.createElement("img");
newImg.setAttribute("src", url);
return newImg;
}
html, body {
height: 100%;
}
button {
position: relative;
z-index: 1
width: auto;
height: 50px;
}
.container {
display: flex;
justify-content: center;
position: relative;
top: 15%;
z-index: 0
}
.each-result {
height: 150px;
width: calc(10% + 20px);
margin: 0 20px;
border: 3px dotted red;
}
img {
height: 100%;
width: auto;
}
.nav-arrows {
display: flex;
justify-content: space-between;
width: 100%;
height: auto;
position: absolute;
background: clear;
pointer-events: none;
}
#left-arrow, #right-arrow {
pointer-events: auto;
}
#root-container {
display: flex;
align-items: center;
border: 1px solid black;
height: 200px;
position: relative;
flex-flow: row no-wrap;
/* overflow: hidden; */
width: 100%;
}
.result-container {
display: flex;
border: 2px blue dotted;
}
<script src="https://use.fontawesome.com/releases/v5.0.6/js/all.js"></script>
<div class="container">
<div class="nav-arrows">
<button id="left-arrow"><i class="fas fa-arrow-alt-circle-left"></i>
</button>
<button id="right-arrow"> <i class="fas fa-arrow-alt-circle-right"></i>
</button>
</div>
<div id="root-container">
<div id="root" class="result-container">
</div>
</div>
</div>
<button id="button-generate">Generate Content</button>

How create a grid in which a variable number of squares automatically resize to fit a space?

I'm working on this problem: http://www.theodinproject.com/web-development-101/javascript-and-jquery?ref=lnav.
Basically, I need to create a game in which the user clicks a button, is asked how many squares they want in their grid, and the grid appears and they can draw on the grid by mousing over the squares which changes their color. So far, I figured out everything Except how to automatically resize the squares to take up the full space of the div container they are in (I restricted the shape of the 16x16 grid to a square by placing it in a div with the id container that had a set height and width).
I tried to play around with the css, changing the height and width to 100%. When I do that though, they just appear in a column and not a grid.
If you open this up in jsfiddle, and request a grid of 16 squares per side, you will see what I WANT to have happen. 16 squares per side is the max, and I would like any lower number of squares to fill up that space and stay in a square grid format, not form a column. Im unsure of whether the solution involves CSS or jQuery. Thank you for any help!
Here is the html:
<body>
<div id ="button">
<p>Play the game</p>
</div>
<div id ="container"></div>
</div>
</body>
Here is the CSS:
.squares {
background-color: #c266ff;
display: block;
height: 100%;
width: 100%;
display: inline-block;
margin-left: 3px;
}
#container {
height: 200px;
width: 470px;
margin: auto;
margin-top: 50px;
}
#button {
height: 50px;
width: 100px;
background-color: #ddcee6;
border: 2px solid #ddcee6;
border-radius: 10px;
text-align: center;
color: #19171a;
margin: auto;
margin-top: 50px;
}
Here is the jquery/javascript:
$(document).ready(function() {
$("#button").click(function() {
var x = prompt("How many squares do you want on each side of the grid? Pick between 1 and 16.");
for(var i = 0; i < (x*x); i++) {
$("<div class='squares'></div>").appendTo("#container");
}
$(".squares").one("mouseover", function() {
$(this).css("background-color","#6b00b3");
});
});
});
Here is a way that you can calculate the height and width, and apply it, using jQuery.
//Calculate squares height and width
var containerHeight = $('#container').innerHeight();
var containerWidth = $('#container').innerWidth();
var squareMargins = ($('.squares').outerWidth(true) - $('.squares').outerWidth()) * x; //Margin * x to take margin space into account, otherwise calculation will be off
var squareHeight = (containerHeight - squareMargins) / x;
var squareWidth = (containerWidth - squareMargins) / x;
$('.squares').height(squareHeight);
$('.squares').width(squareWidth);
Demo Here
Here is a start.
$(document).ready(function() {
$("#button").click(function() {
var x = prompt("How many squares do you want on each side of the grid? Pick between 1 and 16.");
var s = (100 / x);
for(var i = 0; i < (x*x); i++) {
$("<div class='squares' style='width:" + s + "%; height:" + s + "%'></div>").appendTo("#container");
}
$(".squares").one("mouseover", function() {
$(this).css("background-color","#6b00b3");
});
console.log(document.body.innerHTML);
});
});
.squares {
background-color: #c266ff;
float: left;
border: 1px solid black;
box-sizing: border-box;
}
#container {
height: 200px;
width: 470px;
margin: auto;
margin-top: 50px;
}
#button {
height: 50px;
width: 100px;
background-color: #ddcee6;
border: 2px solid #ddcee6;
border-radius: 10px;
text-align: center;
color: #19171a;
margin: auto;
margin-top: 50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id ="button">
<p>Play the game</p>
</div>
<div id ="container">
</div>

Categories