My question is about increase the width of a div inside a li.
Basically, when a user scrolls, the sidebar (Right Hand Side) li increase the span (red background color) from 0 to 100. In other words, if the first corresponding div is in view, then the corresponding li elements' span will increase/decrease from 0 to 100% as a user scrolls down or up. This will indicate how much of the div the user has scrolled.
I have a sample code pen that shows what I am trying to do
https://codepen.io/hellouniverse/pen/xdVJQp
My CSS looks like
.sidebar {
position: fixed;
max-width: 300px;
&__list {
position: relative;
text-align: center;
padding: 10px;
margin: 10px auto;
border: 2px solid #f3f2f0;
list-style-type: none;
display: inline-flex;
flex-direction: column;
}
// The primary red bar at the bottom that increases in width with scroll
span {
background-color: red;
height: 5px;
}
}
The problem is in the js
In the JS, I guess
1. Need to calculate height of the corresponding div
2. Increase the span by the certain percentage depending on the height calculated on 1.
I can not seem to figure out the code at all. Can anyone help?
// Checks whether an elem gets into view of the window
function isScrolledIntoView(elem) {
'use strict';
if (elem.length > 0) {
var docViewTop = jQuery(window).scrollTop();
var docViewBottom = docViewTop + jQuery(window).height();
var elemTop = jQuery(elem).offset().top;
return ((elemTop <= docViewBottom) && (elemTop >= docViewTop));
}
}
var lastScrollTop = 0;
$(window).scroll(function() {
// Increase & Decrease width of the span of li as you scroll
var height = $('section-just-in').height();
var incrementBy = 300 / height;
$('.sidebar__list span').animate({
// increase width from 0 till 100%
width : width + incrementBy
});
})
something like this:
https://codepen.io/hdl881127/pen/ZKOaBY
or jsfiddle here:
https://jsfiddle.net/dalinhuang/Lw72csjd/
1st get height for each section
2nd on scroll, check the progress for each section
last apply the progress bar to each button.
$(document).ready(function() {
var theone = $('#section-just-in');
var thetwo = $('#videos');
var thethree = $('#aboutteam');
var theone_h, thetwo_h, thethree_h;
theone_h = theone.innerHeight();
thetwo_h = thetwo.innerHeight();
// remove the window height cus
// we are not able to full scroll down when we hit the bottom of the page
thethree_h = thethree.innerHeight() - $(window).height();
$(document).scroll(function() {
var page_height = $("body").scrollTop();
var pos_2 = page_height > theone_h ? page_height - theone_h : 0;
var pos_3 = page_height > (thetwo_h+theone_h) ? (page_height - (+thetwo_h + +theone_h)) : 0;
var progress_1 = (page_height / theone_h) >= 1 ? 100 : (page_height / theone_h) * 100;
var progress_2 = (pos_2 / thetwo_h) >= 1 ? 100 : (pos_2 / thetwo_h) * 100;
var progress_3 = ((pos_3) / thethree_h) >= 1 ? 100 : ((pos_3) / thethree_h) * 100;
$("ul li:nth-child(1) span").stop().animate({
"width": progress_1 + '%'
}, 0);
$("ul li:nth-child(2) span").stop().animate({
"width": progress_2 + '%'
}, 0);
$("ul li:nth-child(3) span").stop().animate({
"width": progress_3 + '%'
}, 0);
});
// Checks whether an elem gets into view of the window
function isScrolledIntoView(elem) {
'use strict';
if (elem.length > 0) {
var docViewTop = jQuery(window).scrollTop();
var docViewBottom = docViewTop + jQuery(window).height();
var elemTop = jQuery(elem).offset().top;
return ((elemTop <= docViewBottom) && (elemTop >= docViewTop));
}
}
});
Here you go:
var height,
incrementBy,
visible_height;
$(document).ready(function(){
visible_height = $('.section-just-in').innerHeight();
});
$(document).scroll(function() {
var scroll_top = Math.max( $("html").scrollTop(), $("body").scrollTop());
incrementBy = (100 * scroll_top) / visible_height / 1.4;
$('.sidebar__list span').stop().animate({
"width" : incrementBy
});
});
Just replace everything after isScrolledIntoView function and it should work.
Related
I'm trying to animate a div on scroll. The point is that the div's width must grow until it reaches 80vw and stop. This does happen, but my variable keeps on growing (it's being logged to the console) even if the >=outerWidth*0.8 condition isn't met. Thanks to this, whenever I get to 80vw and scroll up and then down, the width becomes Xvw.
$(window).on('scroll',function(){
var scrollTop = $(this).scrollTop();
var outerHeight = $(this).outerHeight();
var outerWidth = $(this).outerWidth();
var scrollBottom = scrollTop + outerHeight;
var scrollTop = $(this).scrollTop();
console.log( growNaranja );
if (scrollTop > lastScrollTop){ // scroll down
if( naranjaWidth <= (outerWidth*0.8) ){
growNaranja = (naranja.outerWidth()*100) / outerWidth;
growNaranja = growNaranja+(scrollTop*0.05);
$('.grow.naranja').css( 'width', growNaranja + 'vw' );
}
} else { // scroll up
if( naranjaWidth >= (outerWidth*0.1) ){
growNaranja = (naranja.outerWidth()*100) / outerWidth;
$('.grow.naranja').css( 'width', growNaranja + 'vw' );
growNaranja = growNaranja - ((lastScrollTop-scrollTop)*0.05);
$('.grow.naranja').css( 'width', growNaranja + 'vw' );
}
}
lastScrollTop = scrollTop;
});
You can see a working example here.
Revisited this one, it was bugging me. First, the code was all spaghetti. Second, there was really function duplication. You had a function for scrolling up and one for scrolling down, and you were using the last scrollTop to calculate the next scroll step. Instead, I've made a single scale function that gets called regardless. The value of the percentage scrolled is multiplied by the step factor, and that is added to the ORIGINAL element width. By doing this, I'm not worried about where I was just prior to the scroll, only where I am now.
So I made the scaleWidthEl an object constructor, and simply wrapped the naranja div in that. The actual code to create it is the first three lines, and could be reduced to:
var scaleNaranja = new ScaleWidthEl($('.grow.naranja'), 0.8);
The rest is self-contained, allowing changes to be made without affecting anything else.
var maxElScale = 0.8;
var naranja = $('.grow.naranja');
var scaleNaranja = new ScaleWidthEl(naranja, maxElScale);
/***
* The rest of this is a black-box function, walled away from the main code
* It's a personal peeve of mine that code gets garbled otherwise.
***/
function ScaleWidthEl(el, maxScale) {
// I don't need a minScale, as I use the initial width for that
this.el = el;
this.vwConversion = (100 / document.documentElement.clientWidth);
this.startingWidth = el.outerWidth();
this.maxScale = maxScale;
this.max = $(window).outerWidth() * this.maxScale;
this.step = (this.max - this.startingWidth) / $(window).outerHeight();
// for the sake of clarity, store a reference to `this` for
// any nested functions.
var that = this;
/**
* function scaleEl
* handle the actual scaling of the element.
* Using a given step, we will simply add that
* to the element's current width, then update the CSS
* width property of the element.
**/
this.scaleEl = function() {
// First, calculate the percentage of vertical scroll
var winheight = $(window).height();
var docheight = $(document).height();
var scrollTop = $(window).scrollTop();
var trackLength = docheight - winheight;
// gets percentage scrolled (ie: 80 NaN if tracklength == 0)
var pctScrolled = Math.floor(scrollTop / trackLength * 100);
// console.log(pctScrolled + '% scrolled')
// Now, using the scrolled percentage, scale the div
var tempWidth = this.startingWidth * this.vwConversion;
tempWidth += pctScrolled * this.step;
// I want to fix the max of the scale
if (tempWidth > (this.maxScale * 100)) {
tempWidth = this.maxScale * 100;
}
this.el.css('width', tempWidth + 'vw');
};
$(window).on("scroll", function() {
that.scaleEl();
}).on("resize", function() {
/**
* In the case of a resize, we should
* recalculate min, max and step.
**/
that.min = $(window).outerWidth() * that.minScale;
that.max = $(window).outerWidth() * that.maxScale;
that.step = (that.max - that.min) / $(window).outerHeight();
})
}
body {
height: 10000px;
}
.grow {
position: fixed;
height: 100vh;
top: 0;
left: 0;
}
.grow.gris {
width: 35vw;
z-index: 2;
background: silver;
}
.grow.naranja {
width: 10vw;
z-index: 1;
background: orange;
}
<script src="https://code.jquery.com/jquery-3.1.1.min.js" crossorigin="anonymous"></script>
<div class="grow naranja"></div>
<!-- .naranja -->
I would like to get the one element which is the most visible on the screen (takes up the most space). I have added an example picture below to understand my question a bit more.
The two black borders are the sides of a screen. As you can see, the green box (div2) is the most visible on the screen - I would like to know how I can get that element. The most visible element should not have to be fully visible.
I have done a quick (it wasn't THAT quick) seach but to no avail, if I have missed it - my apologies.
TLDR:
Inspired by this question and the necessity for similar functionality in my own projects, I've written a module/jQuery plugin based on the code below. If you're not interested in the 'how', just download that or install with your favourite package manager.
Original Answer:
The answer provided by exabyssus works well in most cases, apart from when neither of an element's top or bottom is visible e.g when the element height is greater than the window height.
Here's an updated version which takes that scenario into account and uses getBoundingClientRect which is supported right the way down to IE8:
// Usage: var $element = getMostVisible($('.elements' ));
function getMostVisible($elements) {
var element,
viewportHeight = $(window).height(),
max = 0;
$elements.each(function() {
var visiblePx = getVisibleHeightPx($(this), viewportHeight);
if (visiblePx > max) {
max = visiblePx;
element = this;
}
});
return $elements.filter(element);
}
function getVisibleHeightPx($element, viewportHeight) {
var rect = $element.get(0).getBoundingClientRect(),
height = rect.bottom - rect.top,
visible = {
top: rect.top >= 0 && rect.top < viewportHeight,
bottom: rect.bottom > 0 && rect.bottom < viewportHeight
},
visiblePx = 0;
if (visible.top && visible.bottom) {
// Whole element is visible
visiblePx = height;
} else if (visible.top) {
visiblePx = viewportHeight - rect.top;
} else if (visible.bottom) {
visiblePx = rect.bottom;
} else if (height > viewportHeight && rect.top < 0) {
var absTop = Math.abs(rect.top);
if (absTop < height) {
// Part of the element is visible
visiblePx = height - absTop;
}
}
return visiblePx;
}
This returns the most visible element based on pixels rather than as a percentage of the height of the element, which was ideal for my use-case. It could easily be modified to return a percentage if desired.
You could also use this as a jQuery plugin so you can get the most visible element with $('.elements').mostVisible() rather than passing the elements to the function. To do that, you'd just need to include this with the two functions above:
$.fn.mostVisible = function() {
return getMostVisible(this);
};
With that in place you can chain your method calls rather than having to save the element into a variable:
$('.elements').mostVisible().addClass('most-visible').html('I am most visible!');
Here's all of that wrapped up in a little demo you can try out right here on SO:
(function($) {
'use strict';
$(function() {
$(window).on('scroll', function() {
$('.the-divs div').html('').removeClass('most-visible').mostVisible().addClass('most-visible').html('I am most visible!');
});
});
function getMostVisible($elements) {
var element,
viewportHeight = $(window).height(),
max = 0;
$elements.each(function() {
var visiblePx = getVisibleHeightPx($(this), viewportHeight);
if (visiblePx > max) {
max = visiblePx;
element = this;
}
});
return $elements.filter(element);
}
function getVisibleHeightPx($element, viewportHeight) {
var rect = $element.get(0).getBoundingClientRect(),
height = rect.bottom - rect.top,
visible = {
top: rect.top >= 0 && rect.top < viewportHeight,
bottom: rect.bottom > 0 && rect.bottom < viewportHeight
},
visiblePx = 0;
if (visible.top && visible.bottom) {
// Whole element is visible
visiblePx = height;
} else if (visible.top) {
visiblePx = viewportHeight - rect.top;
} else if (visible.bottom) {
visiblePx = rect.bottom;
} else if (height > viewportHeight && rect.top < 0) {
var absTop = Math.abs(rect.top);
if (absTop < height) {
// Part of the element is visible
visiblePx = height - absTop;
}
}
return visiblePx;
}
$.fn.mostVisible = function() {
return getMostVisible(this);
}
})(jQuery);
.top {
height: 900px;
background-color: #999
}
.middle {
height: 200px;
background-color: #eee
}
.bottom {
height: 600px;
background-color: #666
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="the-divs">
<div class="top"></div>
<div class="middle"></div>
<div class="bottom"></div>
</div>
Yes, this question is too broad. But I was interested on solving it.
Here is crude example on how to accomplish it.
I tried to explain what's going on with comments. It surely can be done better, but I hope it helps.
// init on page ready
$(function() {
// check on each scroll event
$(window).scroll(function(){
// elements to be tested
var _elements = $('.ele');
// get most visible element (result)
var ele = findMostVisible(_elements);
});
});
function findMostVisible(_elements) {
// find window top and bottom position.
var wtop = $(window).scrollTop();
var wbottom = wtop + $(window).height();
var max = 0; // use to store value for testing
var maxEle = false; // use to store most visible element
// find percentage visible of each element
_elements.each(function(){
// get top and bottom position of the current element
var top = $(this).offset().top;
var bottom = top + $(this).height();
// get percentage of the current element
var cur = eleVisible(top, bottom, wtop, wbottom);
// if current element is more visible than previous, change maxEle and test value, max
if(cur > max) {
max = cur;
maxEle = $(this);
}
});
return maxEle;
}
// find visible percentage
function eleVisible(top, bottom, wtop, wbottom) {
var wheight = wbottom - wtop;
// both bottom and top is vissible, so 100%
if(top > wtop && top < wbottom && bottom > wtop && bottom < wbottom)
{
return 100;
}
// only top is visible
if(top > wtop && top < wbottom)
{
return 100 + (wtop - top) / wheight * 100;
}
// only bottom is visible
if(bottom > wtop && bottom < wbottom)
{
return 100 + (bottom - wbottom) / wheight * 100;
}
// element is not visible
return 0;
}
Working example - https://jsfiddle.net/exabyssus/6o30sL24/
<style>
.block{
padding: 20px;
border:2px solid #000;
height: 200px;
overflow:hidden;
}
.green{
border: 1px solid green;
height: 150px;
margin:20px 0px;
}
.red{
border: 1px solid red;
height: 100px;
}
</style>
<div class="block">
<div class="example green"></div>
<div class="example red"></div>
</div>
var divs = $('.example');
var obj = {};
var heights = [];
$.each(divs,function (key, val)
{
heights.push($(val).outerHeight());
obj[$(val).outerHeight()] = $(val);
});
var max = Math.max.apply(null, heights);
console.log(obj[max]);
I'd like to know if there's a way to explore the content of a div by moving mouse? like for example having a 1000px*1000px pic inside a 500px*500px div content in overflow:hidden and being able to see the rest of the picture by putting the cursor in the right-bottom side of the div.
And if there's a way how should I proceed ?
Something nice and smooth?
jQuery(function($) {
const $mmGal = $('#mmGal'),
$mmImg = $('#mmImg'),
damp = 10; // 1 = immediate, higher number = smoother response
let X = 0, Y = 0,
mX = 0, mY = 0,
wDiff = 0, hDiff = 0,
zeno, tOut;
// Get image size after it's loaded
$mmImg.one('load', function() {
wDiff = (this.width / $mmGal.width()) - 1;
hDiff = (this.height / $mmGal.height()) - 1;
}).each(function() {
if (this.complete) $(this).trigger("load");
});
$mmGal.on({
mousemove(ev) {
mX = ev.pageX - this.offsetLeft;
mY = ev.pageY - this.offsetTop;
},
mouseenter() {
clearTimeout(tOut);
clearInterval(zeno);
zeno = setInterval(function() { // Zeno's paradox "catching delay"
X += (mX - X) / damp;
Y += (mY - Y) / damp;
// Use CSS transition
$mmImg.css({transform: `translate(${-X * wDiff}px, ${-Y * hDiff}px)`});
// If instead you want to use scroll:
// $mmGal[0].scrollTo(X * wDiff, Y * hDiff);
}, 26);
},
mouseleave() {
// Allow the image to move for some time even after mouseleave
tOut = setTimeout(function() {
clearInterval(zeno);
}, 1200);
}
});
});
#mmGal {
position: relative;
margin: 0 auto;
width: 500px;
height: 220px;
overflow: hidden;
background: #eee;
}
#mmImg {
display: block;
}
<div id="mmGal">
<img id="mmImg" src="https://i.stack.imgur.com/BfcTY.jpg">
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
Here's another similar approach to mousemove element in opposite direction
http://en.wikipedia.org/wiki/Zeno%27s_paradoxes
give widht and height to div wrapped for the image
here is the DEMO
on :hover add overflow: visible; to the div
This is almost what you want. See this fiddle http://jsfiddle.net/sajith/RM9wK/
HTML
<div id="container"><img src="http://farm4.staticflickr.com/3668/12858161173_8daa0b7e54_b.jpg"/></div>
CSS
#container {
width:300px;
height:300px;
overflow: hidden;
}
#container img {
position: relative;
}
Javascript
$(function() {
$( "#container" ).mousemove(function( event ) {
var width = $("#container img").width();
var height = $("#container img").height();
var divWidth = $("#container").width();
var divHeight = $("#container").height();
var xPos = (width / divWidth - 1) * event.pageX
var yPos = (height / divHeight -1) * event.pageY
$("#container img").css('left', '-'+ xPos+'px');
$("#container img").css('top', '-'+ yPos+'px');
});
});
I would use "triggers" (hot spot) ~ add some small div element and set their position as you want, now when mouse enter trigger some events....
Simple Example: jsfiddle
CSS
div.container {
position:relative;
width:100px;
height:100px;
overflow:hidden;
}
.trigger {
right:0;
bottom:0;
position:absolute;
z-index:2;
width:10px;
height:10px;
background-color:transparent;
}
HTML
<div class='container'>
<img src='http://static.adzerk.net/Advertisers/12f0cc69cd9742faa9c8ee0f7b0d210e.jpg' />
<div class='trigger'></div>
</div>
jQuery
$('.trigger').mouseenter(
function(){
$(this).parent('.container').css({
'width':'220px',
'height':'250px'
});
});
$('.container').mouseleave(
function(){
$(this).css({
'width':'100px',
'height':'100px'
});
});
It's easy to keep a column in my layout fixed so it's always visible, even when the user scrolls down.
It's also easy to only move the column down the page when the page is scrolled down far enough for it to be out of the viewport so it's anchored before scrolling starts.
My problem is, I have left hand column that is taller than the average window so you need to be able to scroll down to see all the content (controls) in the left column but at the same time when you scroll up you want to see the top of the controls again.
Here's a visual of what I want to accomplish:
So the left column is always occupying 100% of the height of the window but as the user scrolls down they can see the bottom of the div, and when they start to scroll up the scrolls up until it reaches the top of the window again. So no matter how far they scroll the page, the top of the div is always nearby.
Is there some jQuery magic to make this happen?
Did you mean something like this? (Demo)
var sidebar = document.getElementById('sidebar');
var sidebarScroll = 0;
var lastScroll = 0;
var topMargin = sidebar.offsetTop;
sidebar.style.bottom = 'auto';
function update() {
var delta = window.scrollY - lastScroll;
sidebarScroll += delta;
lastScroll = window.scrollY;
if(sidebarScroll < 0) {
sidebarScroll = 0;
} else if(sidebarScroll > sidebar.scrollHeight - window.innerHeight + topMargin * 2) {
sidebarScroll = sidebar.scrollHeight - window.innerHeight + topMargin * 2;
}
sidebar.style.marginTop = -sidebarScroll + 'px';
}
document.addEventListener('scroll', update);
window.addEventListener('resize', update);
#sidebar {
background-color: #003;
bottom: 1em;
color: white;
left: 1%;
overflow: auto;
padding: 1em;
position: fixed;
right: 80%;
top: 1em;
}
body {
line-height: 1.6;
margin: 1em;
margin-left: 21%;
}
It almost degrades gracefully, too…
I made a fiddle for you, hope this helps you out abit.
I detect scroll up or scroll down, and set the fixed position accordion to the direction.
http://jsfiddle.net/8eruY/
CSS
aside {
position:fixed;
height:140%;
background-color:red;
width:100px;
top:20px;
left:20px;
}
Javascript
//Detect user scroll down or scroll up in jQuery
var mousewheelevt = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel" //FF doesn't recognize mousewheel as of FF3.x
$('html').bind(mousewheelevt, function(e){
var evt = window.event || e //equalize event object
evt = evt.originalEvent ? evt.originalEvent : evt; //convert to originalEvent if possible
var delta = evt.detail ? evt.detail*(-40) : evt.wheelDelta //check for detail first, because it is used by Opera and FF
if(delta > 0) {
$('aside').css('top', '20px');
$('aside').css('bottom', 'auto');
}
else{
$('aside').css('bottom', '20px');
$('aside').css('top', 'auto');
}
});
http://jsfiddle.net/KCrFe/
or this:
.top-aligned {
position: fixed;
top: 10px;
}
with
var scrollPos
$(window).scroll(function(event){
var pos = $(this).scrollTop();
if ( pos < scrollPos){
$('.sidebar').addClass('top-aligned');
} else {
$('.sidebar').removeClass('top-aligned');
}
scrollPos = pos;
});
I have implemented a parallax scrolling effect based on a tutorial I found. The effect works great. However, when I specify the background images, I am unable to control the y (vertical) axis. This is causing problems because I'm trying to set locations on multiple layered images.
Any thoughts on what's causing the problem?
Here is one external script:
$(document).ready(function(){
$('#nav').localScroll(800);
//.parallax(xPosition, speedFactor, outerHeight) options:
//xPosition - Horizontal position of the element
//inertia - speed to move relative to vertical scroll. Example: 0.1 is one tenth the speed of scrolling, 2 is twice the speed of scrolling
//outerHeight (true/false) - Whether or not jQuery should use it's outerHeight option to determine when a section is in the viewport
$('#mainimagewrapper').parallax("50%", 1.3);
$('#secondaryimagewrapper').parallax("50%", 0.5);
$('.image2').parallax("50%", -0.1);
$('#aboutwrapper').parallax("50%", 1.7);
$('.image4').parallax("50%", 1.5);
})
This is another external script:
(function( $ ){
var $window = $(window);
var windowHeight = $window.height();
$window.resize(function () {
windowHeight = $window.height();
});
$.fn.parallax = function(xpos, speedFactor, outerHeight) {
var $this = $(this);
var getHeight;
var firstTop;
var paddingTop = 0;
//get the starting position of each element to have parallax applied to it
$this.each(function(){
firstTop = $this.offset().top;
});
if (outerHeight) {
getHeight = function(jqo) {
return jqo.outerHeight(true);
};
} else {
getHeight = function(jqo) {
return jqo.height();
};
}
// setup defaults if arguments aren't specified
if (arguments.length < 1 || xpos === null) xpos = "50%";
if (arguments.length < 2 || speedFactor === null) speedFactor = 0.1;
if (arguments.length < 3 || outerHeight === null) outerHeight = true;
// function to be called whenever the window is scrolled or resized
function update(){
var pos = $window.scrollTop();
$this.each(function(){
var $element = $(this);
var top = $element.offset().top;
var height = getHeight($element);
// Check if totally above or totally below viewport
if (top + height < pos || top > pos + windowHeight) {
return;
}
$this.css('backgroundPosition', xpos + " " + Math.round((firstTop - pos) * speedFactor) + "px");
});
}
$window.bind('scroll', update).resize(update);
update();
};
})(jQuery);
Here is the CSS for one section:
#aboutwrapper {
background-image: url(../images/polaroid.png);
background-position: 50% 0;
background-repeat: no-repeat;
background-attachment: fixed;
color: white;
height: 500px;
width: 100%;
margin: 0 auto;
padding: 0;
}
#aboutwrapper .image4 {
background: url(../images/polaroid2.png) 50% 0 no-repeat fixed;
height: 500px;
width: 100%;
margin: 0 auto;
padding: 0;
}
.image3{
margin: 0 auto;
min-width: 970px;
overflow: auto;
width: 970px;
}
Both of these are being called to achieve the parallax scrolling. I really just want to more specifically control the background image locations. I've tried messing with the CSS background position and I've messed with the first javascript snippet as well. No luck.
just a quick shot, have you tried actually placing the images, either in a div or just using the img src tag to actually move the element rather than manipulating the y axis of a background image?