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
});
Related
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.
Is there a way to instantly snap to the top of the page without scrolling, when pressing a button? I saw that there are a few ways to scroll to the top, but they are always kinda smooth-scroll.
Here's how to do it:
// the button is stored in the variable topScroll
// the first parameter in scrollTo is x axis and second is y
topScroll.onclick = () => window.scrollTo(window.scrollX, 0);
//If you don't want to manipulate scrollX
topScroll.onclick = () => window.scrollTo(window.scrollX, 0);
tell me if this works
document.addEventListener("DOMContentLoaded", function(){
// We get the Container Element, to prepend some random Elements to increase the Length.
const containerEl = document.getElementById('container')
// Loop to insert 100 paragraph elements with random Number
for(let i = 0; i < 100; i++){
const newEl = document.createElement('p');
newEl.innerText = Math.floor(Math.random()*1000000);
containerEl.prepend(newEl);
}
// We create a Header Element for the Top page
const titleEl = document.createElement("h1");
titleEl.innerText = "U.S.S Enterprise";
containerEl.prepend(titleEl);
// We jump to the bottom of the Page
const containerHeight = containerEl.offsetHeight;
window.scrollTo(0, containerHeight);
// We add a EventListener for the Button at the bottom of the Page
document.getElementById('scrollTopBtn').addEventListener("click", function(){
// When clicked, we jump to the top of the page
// The User can set a default Behavior for scrolling in the Browser as Accessability
// Setting. If you dont override it, it defaults to auto and using this browser set
// Setting. Therefore we define the behavior as instant.
window.scrollTo({
top: 0,
left: 0,
behavior: 'instant'
});
});
})
<div id="container">
<button id="scrollTopBtn">Beam me up, Scotty!</button>
</div>
Use this function when user click the button:
function scrollToTop(){
document.documentElement.scrollTop = 0;
}
I have a simple chat application on a web page, i have an issue when trying to autoscroll the div only when the scroll bar is at the bottom.
I've tried this:
$("#line").load("x.php");
var d = $('#line');
d.scrollTop(d.prop("scrollHeight"));
var refreshId = setInterval(function(){
var isEnd = $('#line')[0].offsetHeight + $('#line')[0].scrollTop == $('#line')[0].scrollHeight;
$('#line').append('<p class="triangle-isosceles right"><img src="images/user-img.jpg" style="height: 30px;padding-right: 5px;"/><b>Douglas:</b> prueba<br><small>Fecha</small></p>');
console.log(isEnd);
if(isEnd){
var scrolle = $("#line").prop("scrollHeight") - $('#line').height();
$("div#line").scrollTop(scrolle) ;
}
}, 1000);
So when doing this using the append function works great, the issue comes when instead of append i refresh the content of the div using load()
$("#line").load("x.php");
var d = $('#line');
d.scrollTop(d.prop("scrollHeight"));
var refreshId = setInterval(function(){
var isEnd = $('#line')[0].offsetHeight + $('#line')[0].scrollTop == $('#line')[0].scrollHeight;
$("#line").load('ajax/x.php');
console.log(isEnd);
if(isEnd){
var scrolle = $("#line").prop("scrollHeight") - $('#line').height();
$("div#line").scrollTop(scrolle) ;
}
}, 1000);
This seems to break the autoscroll because the div is not autoscrolling on new messages.
Appreciate your help
Finally i achieved my goal by doing this:
var scrolled = false;
$(window).load(function() {
$("#line").load("x.php");
var d = $('#line');
d.scrollTop(d.prop("scrollHeight"));
$("#line").on('scroll', function(){
scrolled=true;
});
var refreshId = setInterval(function(){
$("#line").load('x.php');
if($('#line')[0].offsetHeight + $('#line')[0].scrollTop == $('#linea')[0].scrollHeight){
scrolled=false;
}
updateScroll();
}, 1000);
});
function updateScroll(){
if(!scrolled){
$("#linea").scrollTop($("#linea").prop("scrollHeight"));
}
}
I define a var named scrolled and set it to false (the user didn't scroll the div), after that i load the conversations on the div #line, then automatically scroll to bottom of that div to show the last messages, add a listener to the scroll event, in case the user scroll the div then i set scrolled to true so the div do not scroll automatically.
Do the refresh of the div element every second and check if the current scroll position is equal to the height of the div, if so then set the scrolled to false.
After that i run a function called updateScroll to scroll to the bottom if the scrolled var is false.
Background
I am trying to create an infinite scrolling table inside a fixed position div. The problem is that all the solutions I come across use the window height and document scrollTop to calculate if the user has scrolled to the bottom of the screen.
Problem
I have tried to create a jQuery plugin that can calculate if a user has scrolled to the bottom of a fixed div with overflow: scroll; set.
My approach has been to create a wrapper div (the div with a fixed position and overflow: scroll) that wraps the table, I also place another div at the bottom of the table. I then try calculate if the wrapper.scrollTop() is greater than the bottom div position.top every time the wrapper is scrolled. I then load the new records and append them to the table body.
$.fn.isScrolledTo = function () {
var element = $(this);
var bottom = element.find('.bottom');
$(element).scroll(function () {
if (element.scrollTop() >= bottom.position().top) {
var tableBody = element.find("tbody");
tableBody.append(tableBody.html());
}
});
};
$('.fixed').isScrolledTo();
See Example http://jsfiddle.net/leviputna/v4q3a/
Question
Clearly my current example is not correct. My question is how to I detect when a user has scrolled to the bottom of a fixed div with overflow:scroll set?
Using the bottom element is a bit clunky, I think. Instead, why not use the scrollHeight and height to test once the scrollable area has run out.
$.fn.isScrolledTo = function () {
var element = this,
tableBody = this.find("tbody");
element.scroll(function(){
if( element.scrollTop() >= element[0].scrollHeight-element.height()){
tableBody.append(tableBody.html());
}
});
};
$('.fixed').isScrolledTo();
EDIT (12/30/14):
A DRYer version of the plugin might be much more re-usable:
$.fn.whenScrolledToBottom = function (cback_fxn) {
this.on('scroll',this,function(){
if( ev.data.scrollTop() >= ev.data[0].scrollHeight - ev.data.height()){
return cback_fxn.apply(ev.data, arguments)
}
});
};
Plugin Usage:
var $fixed = $('.fixed'),
$tableBody = $fixed.find("tbody");
$fixed.whenScrolledToBottom(function(){
// Load more data..
$tableBody.append($tableBody.html());
});
I have modified your code to handle the scroll event with a timer threshold:
$.fn.isScrolledTo = function () {
var element = $(this);
var bottom = element.find('.bottom');
$(element).scroll(function(){
if (this.timer) clearTimeout(this.timer);
this.timer=setTimeout(function(){
if( element.scrollTop() >= bottom.position().top){
var tableBody = element.find("tbody");
tableBody.append(tableBody.html());
}
},300);
});
};
$('.fixed').isScrolledTo();
The issue you are having is that as you scroll, new scroll event is being generated. Your code might have other issues, but this is a start.
Is it possible to fire a specific JavaScript event when a certain DIV comes into view on the page?
Say, for example, I have a very large page, like 2500x2500 and I have a 40x40 div that sits at position 1980x1250. The div is not necessarily manually positioned, it could be there due to the content pushing it there. Now, is it possible to run a function when the user scrolls to a point where the div becomes visible?
Not automatically. You would have to catch scroll events and check for it being in view each time by comparing the co-ordinates of the div rectangle with the visible page rectangle.
Here's a minimal example.
<div id="importantdiv">hello</div>
<script type="text/javascript">
function VisibilityMonitor(element, showfn, hidefn) {
var isshown= false;
function check() {
if (rectsIntersect(getPageRect(), getElementRect(element)) !== isshown) {
isshown= !isshown;
isshown? showfn() : hidefn();
}
};
window.onscroll=window.onresize= check;
check();
}
function getPageRect() {
var isquirks= document.compatMode!=='BackCompat';
var page= isquirks? document.documentElement : document.body;
var x= page.scrollLeft;
var y= page.scrollTop;
var w= 'innerWidth' in window? window.innerWidth : page.clientWidth;
var h= 'innerHeight' in window? window.innerHeight : page.clientHeight;
return [x, y, x+w, y+h];
}
function getElementRect(element) {
var x= 0, y= 0;
var w= element.offsetWidth, h= element.offsetHeight;
while (element.offsetParent!==null) {
x+= element.offsetLeft;
y+= element.offsetTop;
element= element.offsetParent;
}
return [x, y, x+w, y+h];
}
function rectsIntersect(a, b) {
return a[0]<b[2] && a[2]>b[0] && a[1]<b[3] && a[3]>b[1];
}
VisibilityMonitor(
document.getElementById('importantdiv'),
function() {
alert('div in view!');
},
function() {
alert('div gone away!');
}
);
</script>
You could improve this by:
making it catch onscroll on all ancestors that have overflow scroll or auto and adjusting the top/left co-ords for their scroll positions
detecting overflow scroll, auto and hidden cropping putting the div off-screen
using addEventListener/attachEvent to allow multiple VisibilityMonitors and other things using the resize/scroll events
some compatibility hacks to getElementRect to make the co-ords more accurate in some cases, and some event unbinding to avoid IE6-7 memory leaks, if you really need to.
Here is a solution that is ideal in 2022. The current top answer only allows you to observe one item, and has performance issues because it fires many times every time the page scrolls.
var observer = new IntersectionObserver(function(entries) {
if(entries[0].isIntersecting === true) {
console.log('Item has just APPEARED!');
} else {
console.log('Item has just DISAPPEARED!');
}
}, { threshold: [0] });
observer.observe(document.querySelector("#DIV-TO-OBSERVE"));
This fires as soon as the item is partially on screen. Changing threshold to 1 will require the item to be fully on screen (so it will never fire if the item is bigger than the viewport). You can do values in between for example 0.25 to fire when at least 1/4 of the item is in view.
Here's an starter example using jQuery:
<html>
<head><title>In View</title></head>
<body>
<div style="text-align:center; font-size:larger" id="top"></div>
<fieldset style="text-align:center; font-size:larger" id="middle">
<legend id="msg"></legend>
<div> </div>
<div id="findme">Here I am!!!</div>
</fieldset>
<div style="text-align:center; font-size:larger" id="bottom"></div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
var $findme = $('#findme'),
$msg = $('#msg');
function Scrolled() {
var findmeOffset = $findme.offset(),
findmeTop = findmeOffset.top,
scrollTop = $(document).scrollTop(),
visibleBottom = window.innerHeight;
if (findmeTop < scrollTop + visibleBottom) {
$msg.text('findme is visible');
}
else {
$msg.text('findme is NOT visible');
}
}
function Setup() {
var $top = $('#top'),
$bottom = $('#bottom');
$top.height(500);
$bottom.height(500);
$(window).scroll(function() {
Scrolled();
});
}
$(document).ready(function() {
Setup();
});
</script>
</body>
</html>
It only notifies once the div comes into view from the bottom. This example does not notify when the div scrolls out of the top.