Can you set a max-background-size with JavaScript? - javascript

I'm using vanilla JS to increase a background-size value as the user scrolls down the page to create a parallax scroll effect. My function is below:
const heroBg = document.getElementById('hero');
window.addEventListener( 'scroll', function() {
while (heroBg.style.backgroundSize <= '100') {
heroBg.style.backgroundSize = 85 + window.pageYOffset/8 + '%';
console.log(heroBg.style.backgroundSize);
if (heroBg.style.backgroundSize === '100') {
console.log('too big');
break;
}
}
});
The trouble I'm having is that the while loop only appears to run once and the image stops growing well before the 'backgroundSize' hits 100.

You can't check for <= if using strings. Also, you can't add numbers and strings; you have to use brackets.
const heroBg = document.getElementById('hero');
window.addEventListener( 'scroll', function() {
while (parseInt(heroBg.style.backgroundSize) <= 100) {
heroBg.style.backgroundSize = (85 + window.pageYOffset/8) + '%';
console.log(heroBg.style.backgroundSize);
if (parseInt(heroBg.style.backgroundSize) == 100) {
console.log('too big');
break;
}
}
});

the background size property has units of measurement, for example, px or %, but it is also a text property, and to perform a comparison operation, you need to first get rid of the units of measurement and then convert to a number. I suggest considering another option for performing the above task
const $body = document.querySelector("body");
let viewportHeight = document.documentElement.clientHeight
const bgSize = _ => $body.style.backgroundSize = `${(document.documentElement.scrollTop + viewportHeight) / document.documentElement.offsetHeight * 100 * (viewportHeight / 100)}px`
bgSize()
window.addEventListener("resize", _ => {
viewportHeight = document.documentElement.clientHeight
bgSize()
})
document.addEventListener("scroll", _ => {
bgSize()
})
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
height: 300vh;
background: radial-gradient(closest-side circle, black 0% 100%, transparent 100%);
background-repeat: no-repeat;
background-position: center;
background-attachment: fixed;
}

Related

How to implement open curtain animation

I am trying to copy an open curtain animation like in the following where the side divs expand horizontally on the background image on scroll.
I have a working example with the following code
import { useState, useEffect, useRef, useCallback } from "react";
import "./styles.css";
export default function App() {
// check window scroll direction
const [y, setY] = useState(null);
const [scrollDirection, setScrollDirection] = useState("");
const boxTwo = useRef(null);
const boxTwoLeft = useRef(null);
const boxTwoRight = useRef(null);
const countRefTranslateX = useRef(0);
// check window scroll direction https://stackoverflow.com/questions/62497110/detect-scroll-direction-in-react-js
const handleScrollDirection = useCallback(
(e) => {
const window = e.currentTarget;
if (y > window.scrollY) {
setScrollDirection("up");
} else if (y < window.scrollY) {
setScrollDirection("down");
}
setY(window.scrollY);
},
[y]
);
const handleScroll = useCallback(() => {
if (boxTwo.current) {
let position = boxTwo.current.getBoundingClientRect();
// checking for partial visibility and if 50 pixels of the element is visible in viewport
if (
position.top + 50 < window.innerHeight &&
position.bottom >= 0 &&
scrollDirection === "down"
) {
countRefTranslateX.current = countRefTranslateX.current + 3;
boxTwoLeft.current.style.transform = `translateX(-${countRefTranslateX.current}px)`;
boxTwoRight.current.style.transform = `translateX(${countRefTranslateX.current}px)`;
} else if (
position.top + 50 < window.innerHeight &&
position.bottom >= 0 &&
scrollDirection === "up"
) {
countRefTranslateX.current = countRefTranslateX.current - 3;
boxTwoLeft.current.style.transform = `translateX(-${countRefTranslateX.current}px)`;
boxTwoRight.current.style.transform = `translateX(${countRefTranslateX.current}px)`;
} else {
countRefTranslateX.current = 0;
boxTwoLeft.current.style.transform = `translateX(-${countRefTranslateX.current}px)`;
boxTwoRight.current.style.transform = `translateX(${countRefTranslateX.current}px)`;
}
}
}, [scrollDirection]);
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, [handleScroll]);
useEffect(() => {
setY(window.scrollY);
window.addEventListener("scroll", handleScrollDirection);
return () => {
window.removeEventListener("scroll", handleScrollDirection);
};
}, [handleScrollDirection]);
return (
<div className="App">
<div className="boxOne"></div>
<div ref={boxTwo} className="boxTwo">
<div ref={boxTwoLeft} className="boxTwoLeft"></div>
<div ref={boxTwoRight} className="boxTwoRight"></div>
</div>
<div className="boxThree"></div>
</div>
);
}
I have two issues.
My right white div keeps going to the left while I scroll up
I cannot get a fixed vertical window breakpoint. My animation continues after the window has scrolled after the point I want to start/stop moving the divs
How can I resolve these issues?
My codesandbox
I've looked over what you've done but it's way too complicated.
All you want is to place two curtains (panels) on top of your content.
The container should have position:relative and the curtains should have position: absolute.
You then declare the scrollLimits in between which they should move. In the example below, 0 is the starting point and window.innerHeight the end point. Replace those values with whatever makes sense for your section, considering its vertical position in the page. You could use the section's current offsetTop and clientHeight to set the limits dynamically, based on current window size.
You then get the current scroll position and calculate the scroll percentage relative to the limits.
You then apply the percentage/2 + 50% to each curtain's transform: translateX() the left one with negative value, the right one with positive value.
Done.
document.addEventListener('scroll', revealSection);
/* function revealSection() {
const scrollLimits = [0, window.innerHeight];
const currentScroll = window.scrollY;
const percent = Math.min(currentScroll * 100 / (scrollLimits[1] - scrollLimits[0]), 100);
document.querySelector('.l-panel').style.transform = `translateX(-${percent/2 + 50}%)`;
document.querySelector('.r-panel').style.transform = `translateX(${percent/2 + 50}%)`;
} */
function revealSection() {
const top = document.querySelector('section').getBoundingClientRect().top;
const percent = Math.min(Math.max(0, (window.innerHeight - top) / window.innerHeight * 100), 100);
document.querySelector('.l-panel').style.transform = `translateX(-${percent/2 + 50}%)`;
document.querySelector('.r-panel').style.transform = `translateX(${percent/2 + 50}%)`;
}
body {
margin: 0;
}
section {
background-image: url('https://static.toss.im/assets/homepage/new tossim/section2_4_big.jpg');
background-repeat: no-repeat;
background-position: center;
background-size: cover;
width: 100%;
height: 100vh;
margin: 100vh 0;
position: relative;
overflow: hidden;
}
.l-panel, .r-panel{
position: absolute;
height: 100%;
width: 100%;
content: '';
top: 0;
background-color: white;
left: 0;
}
<section>
<div class="l-panel"></div>
<div class="r-panel"></div>
</section>
Note: change the CSS selectors so the styles apply to your section only.
Edit: I've come up with a more generic way to set the scroll interval, using the section's getBoundingClientRect().top and window.innerHeight. It's probably more useful, as you no longer have to worry about the section's position in page.
I've left the previous version in, for reference and/or for anyone who prefers it.

Trying to get this smoother and more natural in behavior

My implementation,
http://kodhus.com/kodnest/land/PpNFTgp
I am curious, as I am not able for some reason to figure this out, how to get my JavaScript to make my slider behave more natural and smoother, if someone knows, how to, or can make this, feel free. I'd be happy to understand.
JavaScript:
const thumb = document.querySelector('.thumb');
const thumbIndicator = document.querySelector('.thumb .thumb-indicator');
const sliderContainer = document.querySelector('.slider-container');
const trackProgress = document.querySelector('.track-progress');
const sliderContainerStart = sliderContainer.offsetLeft;
const sliderContainerWidth = sliderContainer.offsetWidth;
var translate;
var dragging = false;
var percentage = 14;
document.addEventListener('mousedown', function(e) {
if (e.target.classList.contains('thumb-indicator')) {
dragging = true;
thumbIndicator.classList.add('focus');
}
});
document.addEventListener('mousemove', function(e) {
if (dragging) {
console.log('moving', e)
if (e.clientX < sliderContainerStart) {
translate = 0;
} else if (e.clientX > sliderContainerWidth + sliderContainerStart) {
translate = sliderContainerWidth;
} else {
translate = e.clientX - sliderContainer.offsetLeft;
}
thumb.style.transform = 'translate(-50%) translate(' + translate + 'px)';
trackProgress.style.transform = 'scaleX(' + translate / sliderContainerWidth + ')'
}
});
function setPercentage() {
thumb.style.transform = 'translate(-50%) translate(' + percentage/100 * sliderContainerWidth + 'px)';
trackProgress.style.transform = 'scaleX(' + percentage/100 + ')';
}
function init() {
setPercentage();
}
init();
document.addEventListener('mouseup', function(e) {
dragging = false;
thumbIndicator.classList.remove('focus');
});
EDIT: Is there a way to smoothly and naturally increment by one for every slow move?
Is it possible to make to behave as if, like when one clicks the progress bar so that it jumps there?
The kodhus site is very janky in my browser, so I can't tell if your code lacks responsiveness or whether it's the site itself. I feel that your code is a bit convoluted: translate and width / height are mixed unnecessarily; no need to use a dragging boolean when that information is always stored in the classlist. The following slider performs nicely, and has a few considerations I don't see in yours:
stopPropagation when clicking the .thumb element
drag stops if window loses focus
pointer-events: none; applied to every part of the slider but the .thumb element
let applySliderFeel = (slider, valueChangeCallback=()=>{}) => {
// Now `thumb`, `bar` and `slider` are the elements that concern us
let [ thumb, bar ] = [ '.thumb', '.bar' ].map(v => slider.querySelector(v));
let changed = amt => {
thumb.style.left = `${amt * 100}%`;
bar.style.width = `${amt * 100}%`;
valueChangeCallback(amt);
};
// Pressing down on `thumb` activates dragging
thumb.addEventListener('mousedown', evt => {
thumb.classList.add('active');
evt.preventDefault();
evt.stopPropagation();
});
// Releasing the mouse button (anywhere) deactivates dragging
document.addEventListener('mouseup', evt => thumb.classList.remove('active'));
// If the window loses focus dragging also stops - this can be a very
// nice quality of life improvement!
window.addEventListener('blur', evt => thumb.classList.remove('active'));
// Now we have to act when the mouse moves...
document.addEventListener('mousemove', evt => {
// If the drag isn't active do nothing!
if (!thumb.classList.contains('active')) return;
// Compute `xRelSlider`, which is the mouse position relative to the
// left side of the slider bar. Note that *client*X is compatible with
// getBounding*Client*Rect, and using these two values we can quickly
// get the relative x position.
let { width, left } = slider.getBoundingClientRect();
// Consider mouse x, subtract left offset of slider, and subtract half
// the width of the thumb (so drags position the center of the thumb,
// not its left side):
let xRelSlider = evt.clientX - left - (thumb.getBoundingClientRect().width >> 1);
// Clamp `xRelSlider` between 0 and the slider's width
if (xRelSlider < 0) xRelSlider = 0;
if (xRelSlider > width) xRelSlider = width;
// Apply styling (using percents is more robust!)
changed(xRelSlider / width);
evt.preventDefault();
evt.stopPropagation();
});
slider.addEventListener('mousedown', evt => {
let { width, left } = slider.getBoundingClientRect();
// Clicking the slider also activates a drag
thumb.classList.add('active');
// Consider mouse x, subtract left offset of slider, and subtract half
// the width of the thumb (so drags position the center of the thumb,
// not its left side):
let xRelSlider = evt.clientX - left - (thumb.getBoundingClientRect().width >> 1);
// Apply styling (using percents is more robust!)
changed(xRelSlider / width);
evt.preventDefault();
evt.stopPropagation();
});
changed(0);
};
let valElem = document.querySelector('.value');
applySliderFeel(document.querySelector('.slider'), amt => valElem.innerHTML = amt.toFixed(3));
.slider {
position: absolute;
width: 80%; height: 4px; background-color: rgba(0, 0, 0, 0.3);
left: 10%; top: 50%; margin-top: -2px;
}
.slider > .bar {
position: absolute;
left: 0; top: 0; width: 0; height: 100%;
background-color: #000;
pointer-events: none;
}
.slider > .thumb {
position: absolute;
width: 20px; height: 20px; background-color: #000; border-radius: 100%;
left: 0; top: 50%; margin-top: -10px;
}
.slider > .thumb.active {
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.5);
}
<div class="slider">
<div class="bar"></div>
<div class="thumb"></div>
</div>
<div class="value"></div>

Determine how much of the viewport is covered by element (IntersectionObserver)

I'm using the IntersectionObserver to add and remove classes to elements as they enter the viewport.
Instead of saying "when X% of the element is visible - add this class" I would like to say "when X% of the element is visible or when X% of the viewport is covered by the element - add this class".
I'm assuming this isn't possible? If so I think it's a bit of a flaw with the IntersectionObserver because if you have an element that's 10 times taller than the viewport it'll never count as visible unless you set the threshold to 10% or less. And when you have variable height elements, especially in a responsive design, you'll have to set the threshold to something like 0.1% to be "sure" the element will receive the class (you can never be truly sure though).
Edit: In response to Mose's reply.
Edit2: Updated with several thresholds to force it to calculate percentOfViewport more often. Still not ideal.
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
var entryBCR = entry.target.getBoundingClientRect();
var percentOfViewport = ((entryBCR.width * entryBCR.height) * entry.intersectionRatio) / ((window.innerWidth * window.innerHeight) / 100);
console.log(entry.target.id + ' covers ' + percentOfViewport + '% of the viewport and is ' + (entry.intersectionRatio * 100) + '% visible');
if (entry.intersectionRatio > 0.25) {
entry.target.style.background = 'red';
}
else if (percentOfViewport > 50) {
entry.target.style.background = 'green';
}
else {
entry.target.style.background = 'lightgray';
}
});
}, {threshold: [0.025, 0.05, 0.075, 0.1, 0.25]});
document.querySelectorAll('#header, #tall-content').forEach(function (el) {
observer.observe(el);
});
#header {background: lightgray; min-height: 200px}
#tall-content {background: lightgray; min-height: 2000px}
<header id="header"><h1>Site header</h1></header>
<section id="tall-content">I'm a super tall section. Depending on your resolution the IntersectionObserver will never consider this element visible and thus the percentOfViewport isn't re-calculated.</section>
What you need to do is give each element a different threshold. If the element is shorter than the default threshold (in relation to the window) then the default threshold works fine, but if it's taller you need a unique threshold for that element.
Say you want to trigger elements that are either:
50% visible or
Covering 50% of the screen
Then you need to check:
If the element is shorter than 50% of the window you can use option 1
If the element is taller than 50% of the window you need to give it a threshold that is the windows' height divided by the height of the element multiplied by the threshold (50%):
function doTheThing (el) {
el.classList.add('in-view');
}
const threshold = 0.5;
document.querySelectorAll('section').forEach(el => {
const elHeight = el.getBoundingClientRect().height;
var th = threshold;
// The element is too tall to ever hit the threshold - change threshold
if (elHeight > (window.innerHeight * threshold)) {
th = ((window.innerHeight * threshold) / elHeight) * threshold;
}
new IntersectionObserver(iEls => iEls.forEach(iEl => doTheThing(iEl)), {threshold: th}).observe(el);
});
let optionsViewPort = {
root: document.querySelector('#viewport'), // assuming the viewport has an id "viewport"
rootMargin: '0px',
threshold: 1.0
}
let observerViewport = new IntersectionObserver(callback, optionsViewPort);
observerViewPort.observe(target);
In callback, given the size of the viewport, given the size of the element, given the % of overlapping, you can calculate the percent overlapped in viewport:
const percentViewPort = viewPortSquarePixel/100;
const percentOverlapped = (targetSquarePixel * percent ) / percentViewPort;
Example:
const target = document.querySelector('#target');
const viewport = document.querySelector('#viewport');
const optionsViewPort = {
root: viewport, // assuming the viewport has an id "viewport"
rootMargin: '0px',
threshold: 1.0
}
let callback = (entries, observer) => {
entries.forEach(entry => {
const percentViewPort = (parseInt(getComputedStyle(viewport).width) * parseInt(getComputedStyle(viewport).height))/100;
const percentOverlapped = ((parseInt(getComputedStyle(target).width) * parseInt(getComputedStyle(viewport).height)) * entry.intersectionRatio) / percentViewPort;
console.log("% viewport overlapped", percentOverlapped);
console.log("% of element in viewport", entry.intersectionRatio*100);
// Put here the code to evaluate percentOverlapped and target visibility to apply the desired class
});
};
let observerViewport = new IntersectionObserver(callback, optionsViewPort);
observerViewport.observe(target);
#viewport {
width: 900px;
height: 900px;
background: yellow;
position: relative;
}
#target {
position: absolute;
left: 860px;
width: 100px;
height: 100px;
z-index: 99;
background-color: red;
}
<div id="viewport">
<div id="target" />
</div>
Alternate math to calculate overlap area/percent of target with getBoundingClientRect()
const target = document.querySelector('#target');
const viewport = document.querySelector('#viewport');
const rect1 = viewport.getBoundingClientRect();
const rect2 = target.getBoundingClientRect();
const rect1Area = rect1.width * rect1.height;
const rect2Area = rect2.width * rect2.height;
const x_overlap = Math.max(0, Math.min(rect1.right, rect2.right) - Math.max(rect1.left, rect2.left));
const y_overlap = Math.max(0, Math.min(rect1.bottom, rect2.bottom) - Math.max(rect1.top, rect2.top));
const overlapArea = x_overlap * y_overlap;
const overlapPercentOfTarget = overlapArea/(rect2Area/100);
console.log("OVERLAP AREA", overlapArea);
console.log("TARGET VISIBILITY %", overlapPercentOfTarget);
#viewport {
width: 900px;
height: 900px;
background: yellow;
position: relative;
}
#target {
position: absolute;
left: 860px;
width: 100px;
height: 100px;
z-index: 99;
background-color: red;
}
<div id="viewport">
<div id="target" />
</div>
Here is a solution using ResizeObserver that fires the callback when the element is > ratio visible or when it fully covers the viewport.
/**
* Detect when an element is > ratio visible, or when it fully
* covers the viewport.
* Callback is called only once.
* Only works for height / y scrolling.
*/
export function onIntersection(
elem: HTMLElement,
ratio: number = 0.5,
callback: () => void
) {
// This helper is needed because IntersectionObserver doesn't have
// an easy mode for when the elem is taller than the viewport.
// It uses ResizeObserver to re-observe intersection when needed.
const maxRatio = window.innerHeight / elem.getBoundingClientRect().height;
const threshold = maxRatio < ratio ? 0.99 * maxRatio : ratio;
const intersectionObserver = new IntersectionObserver(
(entries) => {
const entry = entries[0];
if (entry.isIntersecting && entry.intersectionRatio >= threshold) {
disconnect();
callback();
}
},
{ threshold: [threshold] }
);
const resizeObserver = new ResizeObserver(() => {
const diff =
maxRatio - window.innerHeight / elem.getBoundingClientRect().height;
if (Math.abs(diff) > 0.0001) {
disconnect();
onIntersection(elem, ratio, callback);
}
});
const disconnect = () => {
intersectionObserver.disconnect();
resizeObserver.disconnect();
};
resizeObserver.observe(elem);
intersectionObserver.observe(elem);
}
Create two intersectionObservers:
Checks if 25% of the element is visible:
run25Percent() {
// if 25% of the element is visible, callback is triggered
const options = {
threshold: 0.25,
};
const changeBackground = (entries) => {
entries.forEach((entry) => {
if (
// check if entry is intersecting (moving into root),
entry.isIntersecting &&
// check if entry moving into root has a intersectionRatio of 25%
entry.intersectionRatio.toFixed(2) == 0.25
) {
// change Backgroundcolor here
}
});
};
const observer = new IntersectionObserver(
changeBackground,
options
);
document.querySelectorAll('#header, #tall-content').forEach(function
(el) {
observer.observe(el);
});
}
Checks if 50% of the screen is covered
run50Percent() {
//transforms the root into a thin line horizonatlly crossing the center of the screen
const options = {
rootMargin: "-49.9% 0px -49.9% 0px",
};
const changeBackground = (entries) => {
entries.forEach((entry) => {
//checks if element is intersecting
if (entry.isIntersecting) {
//change background color here
}
});
};
const observer = new IntersectionObserver(
changeBackground,
options
);
document.querySelectorAll('#header, #tall-content').forEach((el) =>
{
observer.observe(el);
}
}

Get the element which is the most visible on the screen

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]);

y position issue for background images via css and javascript

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?

Categories

Resources