I have this function that translates the element based on the scroll, but I'm using it on several elements, some at the beginning, others at the very end of the page. The problem is that those bottom elements start having their translates movment at the very first scroll, so when the users reaches the end of the page, those elements have been overly moved.
So I tried using the IntersectionObserver to trigger the translate action only when the element is visible. It worked, but the translate calculation still takes the total page scroll to calculate the translate value, so the bottom page elements goes way beyound anyway.
How can I make this scrollY calculation be based only at the scroll made above the element section?
Here's the script:
<script>
function stickyModule() {
let el = document.querySelectorAll('.<?$=elements?>');
el.forEach(function (module) {
function moduleAnimation() {
console.log('scroll event');
}
new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
console.log('entry', entry.isIntersecting)
if (entry.isIntersecting) {
window.addEventListener('scroll', moduleAnimation, true);
let value = window.scrollY;
document.querySelector('.<?=$element?>').style.transform = `translatey(` + value * <?=$value?> + `px)`;
} else {
window.removeEventListener('scroll', moduleAnimation, true);
console.log('else');
}
});
}).observe(module);
})
}
document.addEventListener('DOMContentLoaded', stickyModule);
</script>
I've tried changing let value = window.scrollY; to entry.scrollY; or entries.scrollY, but no value is returned.
Related
I have a list of chat messages inside a div and want to scroll to the bottom each time an element is added. I tried calling a function that selects the last item and uses scrollIntoView().
scrollToElement:function() {
const el = this.$el.getElementsByClassName('message');
if (el) {
el[el.length-1].scrollIntoView({behavior: "smooth"});
}
}
The issue is that it scrolls to the top of the selected element and not to the bottom of it which is needed in order to include the entire element into view.
I expected:
I got:
Every time you append a new chat message to the chat container - you need to scroll the chat container to its bottom edge. You can do that with a simple assignment:
this.$refs.chatContainer.scrollTop = this.$refs.chatContainer.scrollHeight;
Please note that the scrolling must be performed inside $nextTick to ensure that the new chat message has been added to the DOM.
My advice is to use a Vue directive on the chat container which will automatically scroll to the bottom every time a new chat message has been added:
function scrollToBottom(el)
{
el.scrollTop = el.scrollHeight;
}
// Monitors an element and scrolls to the bottom if a new child is added
// (always:false = prevent scrolling if user manually scrolled up)
// <div class="messages" v-chat-scroll="{always: false}">
// <div class="message" v-for="msg in messages">{{ msg }}</div>
// </div>
Vue.directive('chat-scroll',
{
bind: function(el, binding)
{
var timeout, scrolled = false;
el.addEventListener('scroll', function(e)
{
if (timeout) window.clearTimeout(timeout);
timeout = window.setTimeout(function()
{
scrolled = el.scrollTop + el.clientHeight + 1 < el.scrollHeight;
}, 200);
});
(new MutationObserver(function(e)
{
var config = binding.value || {};
var pause = config.always === false && scrolled;
if (pause || e[e.length - 1].addedNodes.length != 1) return;
scrollToBottom(el);
})).observe(el, {childList: true});
},
inserted: scrollToBottom
});
I want to make a full page scroll effect like this site or this site (demo here).
It seems that divs are moving in the different speed or kind of some acceleration so that the later div may scroll first and the div before may scroll later, and makes the later div becomes like a mask that will hide the before div for a little while.
I tried to use scrollIntoView with smooth behavior like this:
const content = document.querySelectorAll('section');
let index = 0;
document.addEventListener('wheel', event => {
var delta = event.wheelDelta;
if (delta < 0) {
index++;
content.forEach((section, i) => {
if (i === index) {
toggleText(i, 'show');
section.scrollIntoView({behavior: "smooth"});
}
})
} else {
index--;
content.forEach((section, i) => {
if (i === index) {
toggleText(i, 'show');
section.scrollIntoView({behavior: "smooth"});
}
})
}
})
Now when scroll event fires, each section moves in the same speed, and the later section cannot hide the before section for a little while.
How to implement this "mask" like effect?
Easiest way to do this would just to add delays after clicking next or prev
var delay = 2500; //2.5 second
setTimeout(function() {
//your code here
}, delay);
Your code will execute in 2.5 seconds.
I am trying to get the top offset element value on scroll and calculate so that I can change the classes for the elements if I have reached the element on scroll. This is the function:
handleScroll () {
const header = document.querySelector('#header');
const content = document.querySelector('#content');
const rect = header.getBoundingClientRect();
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const headerTop = rect.top + scrollTop;
console.log(headerTop);
if (window.scrollY > headerTop) {
header.classList.add('fixed');
content.classList.add('content-margin');
} else {
header.classList.remove('fixed');
content.classList.remove('content-margin');
}
}
},
beforeMount () {
window.addEventListener('scroll', this.handleScroll);
},
beforeDestroy () {
window.removeEventListener('scroll', this.handleScroll);
}
But, on checking the console.log(headerTop);. on scroll, I see that I keep getting the different values for headerTop while I am scrolling, only when I stop scrolling I get the correct value. How can I fix that, to get the correct value on scroll?
The getBoundingClientRect() function will return a box relative to the current window position. When the element is sticky, it's top value will always be 0, or something else fixed.
This can for instance be solved by adding an empty div before the sticky element, and use that as a reference point of offsets.
I am using an event listener for scroll that will change a button from position:fixed to position:relative on the page depending where the user is on the page. I have a pretty straight forward setup, I am using react, when the react component mounts, I add a listener with a throttler for the scroll event. This works great on desktop, however on mobile, I notice the event to change the positioning on the element is not firing until the scroll is done.
Here is the setup I have -
On the component mount, add listener :
componentDidMount() {
window.addEventListener('scroll', this.scrollThrottler);
}
Which calls the throttling function :
let scrollTimeout;
scrollThrottler() {
// pass through throttle designated at 15 fps
if ( !scrollTimeout ) {
scrollTimeout = setTimeout(() => {
scrollTimeout = null;
this.handleScroll();
}, 66);
}
}
And then the function it calls just checks if a certain div at the right point on the DOM to change the class on it like so :
handleScroll() {
const width = this.state.windowWidth ? this.state.windowWidth : window.innerWidth;
const stickyBoundary = this.refs.approved.getBoundingClientRect().top + this.refs.approved.offsetHeight + this.refs.stickyBar.offsetHeight - window.innerHeight;
if ( (width <= 991) && (stickyBoundary > 0)) {
this.refs.rootNode.className = 'row primary-product-block sticky-add-btn';
} else {
this.refs.rootNode.className = 'row primary-product-block';
}
}
So this works great on desktop, however on mobile it looks like it is not snapping into position fixed or relative until after the scroll event is done. Is there anyway to smooth this out? Thanks!
add touchmove event to your code
componentDidMount() {
window.addEventListener('scroll', this.scrollThrottler);
window.addEventListener('touchmove', this.scrollThrottler);
}
Is there a way to get elements which is:
Inside a div with overflow: scroll
Is in viewport
Just like the following picture, where active div (5,6,7,8,9) is orange, and the others is green (1-4 and >10) :
I just want the mousewheel event to add "active" class to div 5,6,7,8,9 (currently in viewport). View my JSFiddle
$('.wrapper').bind('mousewheel', function (e) {
//addClass 'active' here
});
You could do something like this. I would have re-factored it, but only to show the concept.
Firstly I would attach this to scroll event and not mousewheel. There are those among us that likes to use keyboard for scrolling, and you also have the case of dragging the scrollbar. ;) You also have the case of touch devices.
Note that with this I have set overflow:auto; on wrapper, thus no bottom scroll-bar.
With bottom scrollbar you would either have to live with it becoming tagged as in-view a tad to early, or tumble into the world of doing a cross-browser calculating of IE's clientHeight. But the code should hopefully be OK as a starter.
»»Fiddle««
function isView(wrp, elm)
{
var wrpH = $(wrp).height(),
elmH = $(elm).height(),
elmT = $(elm).offset().top;
return elmT >= 0 &&
elmT + elmH < wrpH;
}
$('.wrapper').bind('scroll', function (e) {
$('div.box').each(function(i, e) {
if (isView(".wrapper", this)) {
$(this).addClass('active');
} else {
$(this).removeClass('active');
}
});
});
Note that you should likely refactor in such a way that .wrapper height is only retrieved once per invocation, or if it is static, at page load etc.
Update; a modified version of isView(). Taking position of container into account. This time looking at dolphins in the pool.
»»Fiddle««
function isView(pool, dolphin) {
var poolT = pool.offset().top,
poolH = pool.height(),
dolpH = dolphin.height(),
dolpT = dolphin.offset().top - poolT;
return dolpT >= 0 && dolpT + dolpH <= poolH;
}