On horizontal scroll of overflown div, fill in progress bar - javascript

I have a div which contains an image. The container of this image has overflow:scroll, so that the user can scroll left or right to see the rest of the image.
I've also implemented a progress bar, which should indicate how much of the image remains to scroll. I.e. if the user has scrolled 5% to the right, it'll fill up 5% of the progress bar (and vice versa).
I can get the function working based on scrollHeight, but can't get it working based on scrollWidth.
Where am I going wrong?
window.onscroll = function() {myFunction()};
function myFunction() {
var winScroll = document.body.scrollTop || document.documentElement.scrollTop;
var width = document.documentElement.scrollLeft - document.documentElement.clientWidth;
var scrolled = (winScroll / width) * 100;
document.getElementById("myBar").style.width = scrolled + "%";
}
.imgCont {
background: black;
overflow: scroll;
position: relative;
}
.imgCont img {
width: auto;
max-width: none;
}
.progress-container {
width: 100%;
height: 8px;
background: blue;
}
.progress-bar {
height: 8px;
background: red;
width: 0%;
}
<div class="imgCont">
<img src="https://i.imgur.com/KhWo66L.png">
</div>
<div class="progress-container">
<div class="progress-bar" id="myBar"></div>
</div>

You need to add listeners on the .imgCont element and use it's scrollLeft, scrollWidth and clientWidth properties
let scrEl = document.getElementById("scr-el")
scrEl.addEventListener('scroll', event => {
let scrolled = (scrEl.scrollLeft / (scrEl.scrollWidth - scrEl.clientWidth) ) * 100
document.getElementById("myBar").style.width = scrolled + "%"
});
.imgCont {
background: black;
overflow-x: scroll;
position: relative;
}
.imgCont img {
width: auto;
max-width: none;
}
.progress-container {
width: 100%;
height: 8px;
background: blue;
}
.progress-bar {
height: 8px;
background: red;
width: 0%;
}
<div id="scr-el" class="imgCont">
<img src="https://i.imgur.com/KhWo66L.png">
</div>
<div class="progress-container">
<div class="progress-bar" id="myBar"></div>
</div>

windows.onscroll won't emit any events while you scroll horizontally because scroll is happening in the element with class imgCont.
put an id imgCont
<div class="imgCont" id="imgCont">
<img src="https://i.imgur.com/KhWo66L.png">
</div>
and call the on scroll event as
document.getElementById("imgCont").onscroll

Jquery solution setps:
subtract the visible width of the image and the real image width
var winScroll = $(".imgCont img").width() - $(".imgCont").width();
get the left scroll position
var width = $(".imgCont").scrollLeft();
get the percentage from the width and position
var scrolled = ((width / winScroll) * 100);
Check the snippet:
$(function(){
$(".imgCont").scroll(function(){
var winScroll = $(".imgCont img").width() - $(".imgCont").width();
var width = $(".imgCont").scrollLeft();
var scrolled = ((width / winScroll) * 100);
$("#myBar").width(scrolled + "%");
});
});
.imgCont {
background: black;
overflow: scroll;
position: relative;
height:200px;
}
.imgCont img {
width: auto;
max-width: none;
}
.progress-container {
width: 100%;
height: 8px;
background: blue;
}
.progress-bar {
height: 8px;
background: red;
width: 0%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="imgCont">
<img src="https://static.toiimg.com/photo/msid-67868104/67868104.jpg?1368689">
</div>
<div class="progress-container">
<div class="progress-bar" id="myBar"></div>
</div>

Another solution.
let div = document.getElementById("theDiv")
div.addEventListener('scroll', function(e) {
let inner = window.innerWidth
let left = div.scrollLeft
let sWidth = div.scrollWidth
let total = sWidth - inner
let width = 1 * left / total * 100
if (width >= 100) {
return document.getElementById("myBar").style.width = "100%";
}
document.getElementById("myBar").style.width = `${width}%`;
});
body {
margin: 0;
}
.imgCont {
background: black;
overflow: auto;
position: relative;
}
.imgCont img {
width: auto;
max-width: none;
}
.progress-container {
width: 100%;
height: 8px;
background: blue;
}
.progress-bar {
height: 8px;
background: red;
width: 0%;
}
<div class="imgCont" id="theDiv">
<img src="https://i.imgur.com/KhWo66L.png">
</div>
<div class="progress-container">
<div class="progress-bar" id="myBar"></div>
</div>

Related

Vertical Scroll Indicator

Trying to create a Scroll Indicator, which would not be horizontal, but vertical. The problem is that when I try to start scrolling the bar that indicated the position isn't scaling...
HTML
<div id="scrollbar">
<div id="bar">
</div>
<div>
CSS
#scrollbar
{
width: 1%;
height: 100%;
padding: 0;
margin: 0 0 0 auto;
right: 0;
top: 0;
position: fixed;
visibility: visible;
background-color: transparent;
}
#scrollbar #bar
{
width: inherit;
height: 0%;
background-color: #ccc;
}
JS
var bar = document.getElementById("bar");
window.onscroll = function () { scrollIndicator() };
window.onload = function () { scrollIndicator() };
function scrollIndicator()
{
var winScroll = document.body.scrollTop || document.documentElement.scrollTop;
var height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
var scrolled = 100;
if(height > 0)
{
scrolled = (winScroll / height) * 100;
}
bar.style.height = scrolled + "%";
}
I absolutely cannot find the problem...
your web page doesn't have enough content for it to scroll, hence scroll event isn't firing.
You need to fix the html, #scrollbar is missing a closing div tag and add enough content on the webpage so that it scrolls.
You also need to change the width of #bar from inherit to 100%.
var bar = document.getElementById("bar");
window.onscroll = function() {
scrollIndicator()
};
function scrollIndicator() {
var winScroll = document.body.scrollTop || document.documentElement.scrollTop;
var height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
var scrolled = 100;
if (height > 0) {
scrolled = (winScroll / height) * 100;
}
bar.style.height = scrolled + "%";
}
#scrollbar {
width: 1%;
height: 100%;
padding: 0;
margin: 0 0 0 auto;
right: 0;
top: 0;
position: fixed;
background-color: transparent;
}
#scrollbar #bar {
width: 100%;
height: 0%;
background-color: blue;
}
.section {
height: 100vh;
}
.section:nth-child(even) {
background: green;
}
.section:nth-child(odd) {
background: red;
}
<div id="scrollbar">
<div id="bar"></div>
</div>
<div class="section"></div>
<div class="section"></div>
<div class="section"></div>

Increase two divs height on mouse move

I horizontally split a page of the browser with two divs, and I want to increase the height of a div and reduce it in the other using the mouse position on the y axis. It would enlarge the first div when I'm in the upper part of the page and enlarge the second one when I'm at the bottom, but keeping both divs sum height equal to the height of the page.
This is my code
<html><head>
<meta charset="UTF-8">
<style>
*{
margin: 0 auto;
}
#container{
height: 100vh
}
#alto{
width: 100vw;
height: 50vh;
background-color: mediumpurple;
}
#basso{
width: 100vw;
height: 50vh;
background-color: royalblue;
}
</style>
</head>
<body>
<div id="alto" onMousemove="myFunction()" ></div>
<div id="basso" ></div>
<script>
function myFunction() {
var y = event.clientY + "px";
document.getElementById("basso").style.height = y ;
}
</script>
</body>
</html>
Take a look at this.
var section1 = document.getElementById("section1");
var section2 = document.getElementById("section2");
document.onmousemove = function(event) {
section1.style.height = event.clientY + 'px';
section2.style.height = "calc(100% - "+ event.clientY + 'px';
}
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container{
background: gray;
height: 100vh;
}
#section1, #section2{
height: 50%;
transition: all 0.1s;
}
#section1{
background: hotpink;
}
#section2{
background: pink;
}
<div class="container">
<div id="section1"></div>
<div id="section2"></div>
</div>
You can achieve a simpler version of this effect (ie. without constantly changing heights relative to the mouse position) with CSS alone.
Working Example:
body {
margin: 0;
padding: 0;
}
div {
width: 100vw;
height: 50vh;
margin: 0 auto;
transition: height 0.3s linear;
}
div:hover {
height: 80vh;
}
body:not(:hover) div {
height: 50vh;
}
div:not(:hover) {
height: 20vh;
}
.alto {
background-color: mediumpurple;
}
.basso {
background-color: royalblue;
}
<div class="alto"></div>
<div class="basso" ></div>

Draggable split-pane windows in flexbox can't get past child elements [duplicate]

This question already has answers here:
Why don't flex items shrink past content size?
(5 answers)
Closed 3 years ago.
I implemented my own split-pane with HTML/JS/CSS Flexbox.
I'm having trouble with the splitter in the following case- one of the panels has a fixed size (in px), and the other one is set to grow (flex-grow: 1).
In case the other panel has children with size, it won't scroll to the end. It gets stuck at the size of the children.
Can this be fixed with CSS on the split-pane panels but not on the children?
It's very important for me to use flex as I want to maintain responsiveness of my application, and want to avoid fixed sizes wherever I can.
This is a JSFiddle sample
of my question.
Code snippet given below. Thanks!
function startDrag() {
glass.style = 'display: block;';
glass.addEventListener('mousemove', drag, false);
}
function endDrag() {
glass.removeEventListener('mousemove', drag, false);
glass.style = '';
}
function drag(event) {
var splitter = getSplitter();
var panel = document.getElementById('c2');
var currentWidth = panel.offsetWidth;
var currentLeft = panel.offsetLeft;
panel.style.width = (currentWidth - (event.clientX - currentLeft)) + "px";
}
function getSplitter() {
return document.getElementById('splitter');
}
var con = document.getElementById('container');
var splitter = document.createElement('div');
var glass = document.getElementById('glass');
splitter.className = 'splitter';
splitter.id = 'splitter';
con.insertBefore(splitter, con.lastElementChild);
splitter.addEventListener('mousedown', startDrag, false);
glass.addEventListener('mouseup', endDrag, false);
.container {
display: flex;
border: 1px solid;
width: 500px;
height: 300px;
position: absolute;
}
.c1 {
background-color: blue;
flex: 1;
height: 100%;
}
.c2 {
background-color: green;
width: 150px;
}
.splitter {
width: 20px;
cursor: col-resize;
}
.glass {
height: 100%;
width: 100%;
cursor: col-resize;
display: none;
position: absolute;
}
.grandchild {
background-color: red;
width: 50px;
height: 50px;
}
<div id="container" class="container">
<div id="glass" class="glass"></div>
<div class="c1">
<div class="grandchild"></div>
</div>
<div id="c2" class="c2"></div>
</div>
In case the other panel has children with size, it won't scroll to the end. It gets stuck at the size of the children.
This is because an initial setting of a flex container is min-width: auto on the flex items. This means that a flex item, by default, cannot be smaller than the size of its content.
Can this be fixed with CSS on the split-pane panels but not on the children?
Yes. Override the default with min-width: 0 or with any overflow other than visible:
.c1 {
background-color: blue;
flex: 1;
height: 100%;
overflow: hidden; /* or min-width: 0 */
}
revised fiddle
function startDrag() {
glass.style = 'display: block;';
glass.addEventListener('mousemove', drag, false);
}
function endDrag() {
glass.removeEventListener('mousemove', drag, false);
glass.style = '';
}
function drag(event) {
var splitter = getSplitter();
var panel = document.getElementById('c2');
var currentWidth = panel.offsetWidth;
var currentLeft = panel.offsetLeft;
panel.style.width = (currentWidth - (event.clientX - currentLeft)) + "px";
}
function getSplitter() {
return document.getElementById('splitter');
}
var con = document.getElementById('container');
var splitter = document.createElement('div');
var glass = document.getElementById('glass');
splitter.className = 'splitter';
splitter.id = 'splitter';
con.insertBefore(splitter, con.lastElementChild);
splitter.addEventListener('mousedown', startDrag, false);
glass.addEventListener('mouseup', endDrag, false);
.container {
display: flex;
border: 1px solid;
width: 500px;
height: 300px;
position: absolute;
}
.c1 {
background-color: blue;
flex: 1;
height: 100%;
overflow: hidden;
}
.c2 {
background-color: green;
width: 150px;
}
.splitter {
width: 20px;
cursor: col-resize;
}
.glass {
height: 100%;
width: 100%;
cursor: col-resize;
display: none;
position: absolute;
}
.grandchild {
background-color: red;
width: 50px;
height: 50px;
}
<div id="container" class="container">
<div id="glass" class="glass"></div>
<div class="c1">
<div class="grandchild"></div>
</div>
<div id="c2" class="c2"></div>
</div>
It gets stuck at the size of the children
This is expected behavior when using a flexbox. I guess if you want to scroll to the end then you can use position: absolute for the grandchild relative to c1:
.grandchild {
background-color: red;
width: 50px;
height: 50px;
position: absolute;
top: 0;
left: 0;
}
Give overflow: hidden to c1 too:
.c1 {
background-color: blue;
flex: 1;
height: 100%;
position: relative;
overflow: hidden;
}
Cheers!
function startDrag() {
glass.style = 'display: block;';
glass.addEventListener('mousemove', drag, false);
}
function endDrag() {
glass.removeEventListener('mousemove', drag, false);
glass.style = '';
}
function drag(event) {
var splitter = getSplitter();
var panel = document.getElementById('c2');
var currentWidth = panel.offsetWidth;
var currentLeft = panel.offsetLeft;
panel.style.width = (currentWidth - (event.clientX - currentLeft)) + "px";
}
function getSplitter() {
return document.getElementById('splitter');
}
var con = document.getElementById('container');
var splitter = document.createElement('div');
var glass = document.getElementById('glass');
splitter.className = 'splitter';
splitter.id = 'splitter';
con.insertBefore(splitter, con.lastElementChild);
splitter.addEventListener('mousedown', startDrag, false);
glass.addEventListener('mouseup', endDrag, false);
.container {
display: flex;
border: 1px solid;
width: 500px;
height: 300px;
position: absolute;
}
.c1 {
background-color: blue;
flex: 1;
height: 100%;
position: relative;
overflow: hidden;
}
.c2 {
background-color: green;
width: 150px;
}
.splitter {
width: 20px;
cursor: col-resize;
}
.glass {
height: 100%;
width: 100%;
cursor: col-resize;
display: none;
position: absolute;
}
.grandchild {
background-color: red;
width: 50px;
height: 50px;
position: absolute;
top: 0;
left: 0;
}
<div id="container" class="container">
<div id="glass" class="glass"></div>
<div class="c1">
<div class="grandchild"></div>
</div>
<div id="c2" class="c2"></div>
</div>
Solution:
So I guess your strategy should be to use an absolute grandchild that fills the whole side-panel, and then put the content inside like:
<div class="grandchild">
<div class="content"></div>
</div>
and change these styles:
.grandchild {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.grandchild .content{
background-color: red;
width: 50px;
height: 50px;
}
Example below:
function startDrag() {
glass.style = 'display: block;';
glass.addEventListener('mousemove', drag, false);
}
function endDrag() {
glass.removeEventListener('mousemove', drag, false);
glass.style = '';
}
function drag(event) {
var splitter = getSplitter();
var panel = document.getElementById('c2');
var currentWidth = panel.offsetWidth;
var currentLeft = panel.offsetLeft;
panel.style.width = (currentWidth - (event.clientX - currentLeft)) + "px";
}
function getSplitter() {
return document.getElementById('splitter');
}
var con = document.getElementById('container');
var splitter = document.createElement('div');
var glass = document.getElementById('glass');
splitter.className = 'splitter';
splitter.id = 'splitter';
con.insertBefore(splitter, con.lastElementChild);
splitter.addEventListener('mousedown', startDrag, false);
glass.addEventListener('mouseup', endDrag, false);
.container {
display: flex;
border: 1px solid;
width: 500px;
height: 300px;
position: absolute;
}
.c1 {
background-color: blue;
flex: 1;
height: 100%;
position: relative;
overflow: hidden;
}
.c2 {
background-color: green;
width: 150px;
}
.splitter {
width: 20px;
cursor: col-resize;
}
.glass {
height: 100%;
width: 100%;
cursor: col-resize;
display: none;
position: absolute;
}
.grandchild {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.grandchild .content{
background-color: red;
width: 50px;
height: 50px;
}
<div id="container" class="container">
<div id="glass" class="glass"></div>
<div class="c1">
<div class="grandchild">
<div class="content"></div>
</div>
</div>
<div id="c2" class="c2"></div>
</div>

Make a div scroll with page only in a certain place?

How can it be made so that a div scrolls with the page but only in a certain area of the page?
I can't work out how to do this with CSS for only part of the page, I think javascript may be the only option.
For e.g. There's three sections of a page, Top, Middle and Bottom.
There's a right floated div which should scroll with the user in the middle section and stop scrolling to be 'left in place' at the top of the middle section as well as the bottom of the middle section.
#Top {
background-color: grey;
width: 100%;
height: 400px;
}
#Middle {
background-color: green;
width: 100%;
height: 800px;
}
#Bottom {
background-color: blue;
width: 100%;
height: 400px;
}
#scrolling-section {
background-color: yellow;
width: 30%;
height: 150px;
float: right;
}
<div id="Top">
</div>
<div id="Middle">
<div id="scrolling-section">
This box should scroll along the green section but 'cut-off' and stop scrolling at the top and bottom of the green section
</div>
</div>
<div id="Bottom">
</div>
JSFiddle: fiddle
So here you have solution using jquery:
Listen to the scroll event and calculate how much the scrolling-section goes outside the Middle section while scrolling up / down.
Added position: relative to the scrolling-section.
Adjust the position of the scrolling-section accordingly.
$(document).scroll(function() {
var wrapper = $('#Middle');
var box = $('#scrolling-section');
var offsetTop = - wrapper.offset().top + $(window).scrollTop();
var offsetBottom = wrapper.offset().top - $(window).scrollTop() + wrapper.outerHeight() - box.outerHeight();
if (offsetBottom > 0 && offsetTop < 0) {
box.css({
'top': 0
});
} else if (offsetBottom > 0 && offsetTop > 0) {
box.css({
'top': offsetTop + 'px'
});
} else {
box.offset({
'top': $(window).scrollTop() + offsetBottom
});
}
});
#Top {
background-color: grey;
width: 100%;
height: 400px;
}
#Middle {
background-color: green;
width: 100%;
height: 800px;
}
#Bottom {
background-color: blue;
width: 100%;
height: 400px;
}
#scrolling-section {
position: relative;
background-color: yellow;
width: 30%;
height: 150px;
float: right;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="Top">
</div>
<div id="Middle">
<div id="scrolling-section">
This box should scroll along the green section but 'cut-off' and stop scrolling at the top and bottom of the green section
</div>
</div>
<div id="Bottom">
</div>
Let me know your feedback on this. Thanks!
Some Javascript is needed in order to read the point where you want to change the state of the div you wish to address. You can do this with the getBoundingClientRect() method. I have worked out a fiddle that will show you.
What happens is that you read the position of #Middle. I have added an input field that shows you the value. The change will be when the position hits zero. You then change the CSS properties of the #scrolling-section.
You will see some added readings of the element to ensure that it can be positioned in place and will keep its original width;
var scrollposition = document.getElementById("Middle");
var scrollsection = document.getElementById("scrolling-section");
var scrollsection_offsetLeft = scrollsection.offsetLeft;
var scrollsection_width = scrollsection.offsetWidth;
var valy = document.getElementById("posy");
window.addEventListener("scroll", function(event) {
valy.value = scrollposition.getBoundingClientRect().y || scrollposition.getBoundingClientRect().top;
if (valy.value <= 0) {
scrollsection.style.position = "fixed";
scrollsection.style.top = "0px";
scrollsection.style.left = scrollsection_offsetLeft + "px";
scrollsection.style.width = scrollsection_width + "px";
} else {
scrollsection.style.position = "static";
scrollsection.style.top = "auto";
scrollsection.style.left = "auto";
}
}, false)
#posy {
position: fixed;
top: 0;
}
#Top {
background-color: grey;
width: 100%;
height: 400px;
}
#Middle {
background-color: green;
width: 100%;
height: 800px;
}
#Bottom {
background-color: blue;
width: 100%;
height: 400px;
}
#scrolling-section {
background-color: yellow;
width: 30%;
height: 150px;
float: right;
}
<input type="text" id="posy" />
<div id="Top">
</div>
<div id="Middle">
<div id="scrolling-section">
This box should scroll along the green section but 'cut-off' and stop scrolling at the top and bottom of the green section
</div>
</div>
<div id="Bottom">
</div>
I'm on my mobile but if you add
#scrolling-section {
position: fixed;
background-color: yellow;
width: 30%;
height: 150px;
right: 8px;
}
This will scroll with the page but really there will need to be an event listener that will trigger when #scrolling-section appears on the screen possibly adding the attribute position:fixed; then another event listener when the #bottom appears calculates the size of #middle set margin-top & position:absolute; hope this helps point in the right direction.

Hide Sticky Div Once Scrolling Past Next Parent Div

I'm trying to hide a "sticky" div once it scrolls past the next parent div. I've currently successfully have it so it appears after scrolling "y > 100" but I'm having a lot of trouble getting the "Sticky Note" to disappear after scrolling past #break.
Example below.
http://codepen.io/anon/pen/BojKBx
$(document).scroll(function() {
var y = $(this).scrollTop();
if (y > 100) {
$('.bottomMenu').fadeIn();
} else {
$('.bottomMenu').fadeOut();
}
});
.bottomMenu {
display: none;
position: fixed;
bottom: 0;
width: 50%;
height: 60px;
border-bottom: 1px solid #000;
z-index: 1;
margin: 0 auto;
left: 50%;
margin-left: -500px;
text-align: center;
}
#header {
font-size: 50px;
text-align: center;
height: 60px;
width: 100%;
background-color: red;
}
#container {
height: 2500px;
}
#break {
text-align: center;
font-size: 30px;
margin-bottom: 300px;
width: 100%;
background-color: yellow;
}
#footer {
height: 60px;
background-color: red;
width: 100%;
bottom: 0;
}
<div id="header">Home</div>
<div class="bottomMenu">
<h2>Sticky Note</h2>
</div>
<div id="container"></div>
<div id="break">Should Not Be Seen After This Point</div>
<div id="footer"></div>
You can get Y position of a div (its vertical offset starting from the top of the page), and then add condition to show sticky note only when you're below the required "Y" coordinate, and above the required div. Example:
http://codepen.io/anon/pen/EVPKyP
Javascript code:
$(document).scroll(function () {
var bodyRect = document.body.getBoundingClientRect(),
elemRect = document.getElementById("break").getBoundingClientRect(),
offset = elemRect.top - bodyRect.top - window.innerHeight;
var y = $(this).scrollTop();
if (y > 100 && y < offset) {
$('.bottomMenu').fadeIn();
} else {
$('.bottomMenu').fadeOut();
}
});
Sources:
Retrieve the position (X,Y) of an HTML element
screen width vs visible portion

Categories

Resources