reinit flickity after page update by ajax call - javascript

I'm giving flickity by metafizzy a try. Working great but after updating the page the newly loaded gallery won't function. How can I reinitialize flickity after ajax load?
I use the js-flickity class to initialize the script.
<div class="gallery js-flickity">
...
</div>

I know it's a bit too late, but I'll post it anyway as it might help someone else.
Haven't tried the resize solution submitted above, but this is how I did it.
After you append your elements to your container, look for the js-flickity elements, see if you can get the object data using data method, and if it's undefined initialise flickity on that element.
var nodeList = document.querySelectorAll('.js-flickity');
for (var i = 0, t = nodeList.length; i < t; i++) {
var flkty = Flickity.data(nodeList[i]);
if (!flkty) {
new Flickity(nodeList[i]);
}
}
Update:
Noticed that there's still quite few people still looking at this question. So here's an updated solution, that also supports flickity carousel options defined in data attribute (optional).
var nodeList = document.querySelectorAll('.js-flickity');
for (var i = 0, t = nodeList.length; i < t; i++) {
var flkty = Flickity.data(nodeList[i]);
if (!flkty) {
// Check if element had flickity options specified in data attribute.
var flktyData = nodeList[i].getAttribute('data-flickity');
if (flktyData) {
var flktyOptions = JSON.parse(flktyData);
new Flickity(nodeList[i], flktyOptions);
} else {
new Flickity(nodeList[i]);
}
}
}

You can do it like this-
//Destroy
$carousel.flickity('destroy');
//Re-init
$carousel.flickity();
Full example code-
var $carousel = $('.carousel').flickity();
var isFlickity = true;
// toggle Flickity on/off
$('.button--toggle').on( 'click', function()
{
//Clearing previous contents
$carousel.flickity('destroy');
$('.carousel').empty();
$(".carousel").append('<div class="carousel-cell"></div>');
$(".carousel").append('<div class="carousel-cell"></div>');
// init new Flickity
$carousel.flickity();
});
.carousel
{
width: 100%;
height: 200px;
}
.carousel-cell {
width: 66%;
height: 200px;
margin-right: 10px;
margin-bottom: 10px;
background: #8C8;
border-radius: 5px;
counter-increment: carousel-cell;
}
.flickity-enabled .carousel-cell {
margin-bottom: 0;
}
/* cell number */
.carousel-cell:before {
display: block;
text-align: center;
content: counter(carousel-cell);
line-height: 200px;
font-size: 80px;
color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/flickity/2.0.5/flickity.pkgd.min.js" type="text/javascript" charset="utf-8"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/flickity/2.0.5/flickity.min.css">
<button class="button button--toggle">Re Create Flickity</button>
<div class="carousel">
<div class="carousel-cell"></div>
<div class="carousel-cell"></div>
<div class="carousel-cell"></div>
<div class="carousel-cell"></div>
<div class="carousel-cell"></div>
</div>

Try calling resize after the content has loaded:
.flickity('resize');

Happened to me and I figured out you have to make sure you Select an Element or it will just show the last one without functioning.
Do something like this where selectIndex is the slide you want selected by default:
$carousel.flickity('selectCell', selectIndex, true, true);

Here's my solution: on success, we pass in the data response from the AJAX request, then I empty the carousel wrapper (an external div that wraps the whole carousel), and then append the product carousel which initially it'll be empty. Then I append each product through a for loop inside this product carousel. On complete, I load the carousel options, then I create the Flickity object asigned to the carousel, no need to run any other scripts for loading the carousel.
success: function(data) {
let productsCarousel = '<div id="productsCarousel" class="products-slider carousel" products-carousel></div>';
$("#productsWrapper").empty().append(productsCarousel);
for (let i = 0; i < data.products.length; i++) {
// Carousel logic
let foundProduct = '<div class="...classes here..."> <div class="carousel-cell carousel-cell-centered"> ... data.products[i] and other HTML here ...</div></div>';
$("#productsCarousel").append(foundProduct);
}
},
complete: function() {
// Products slider options
var productOptions = {
imagesLoaded: true,
wrapAround: true,
autoPlay: 5000,
prevNextButtons: true,
pageDots: false,
};
// Products carousel setup
var productsCarousel = document.querySelector('#productsCarousel');
var productsFlickity = new Flickity(productsCarousel, productOptions);

Also had problems with this and this post helped me resolve issue for Vanilla JS. Vanilla JS solution - 'reinitializing' Flickity carousel after dynamically cleared and reloaded data and without removing Flickity carousel / reloading page)
function reloadCarousel(data) {
var oldCells = document.getElementsByClassName("carousel-cells");
yourFlickityCarousel.remove(oldCells); // takes an array of elements
for (var i = 0; i < data.length; i++) {
var newCell = processData(data[i]);
yourFlickityCarousel.append(newCell);
}
// this makes sure viewport realigns to first item
// in doing so, normal carousel functionality is restored
flkty.selectCell(0, true);
}
Had a very similar problem problem as well - using Vanilla JS I needed to clear out a a Flickity carousel and repopulate it with new or modified cells via JSON via AJAX Load (some may be edited, others may be deleted, may also have new cells, etc)
Issue was after clearing out the Carousel via flkty.remove() the scrolling was 'stuck' on a single slide. Seems even though the slides were re-appended Flickity still thought 'size' of carousel was the same after the remove. (Could pull it but would always bounce back to same single view).
This ended up working as a good way to just 'clear out existing carousel and refresh it'
source: https://flickity.metafizzy.co/api.html#remove

Related

Apply jQuery to body (background) but not Modal using Bootstrap

I am trying to apply foggy.js (http://nbartlomiej.github.io/foggy/) to the back-drop of Bootstraps Modal. This would allow me to blur every element in the background which I have completed so far but the Modal Popup is also Blurry. So my question is how do I apply the blur to the back-drop and its child elements so the Modal isn't blurred but everything else is.
Here is my code. Note I have some code to start and stop Vimeo as well as start and stop the Carousel.
$(document).ready(function(){
$('.close-video').on('click', function (event) {
for (var i = 0; i < window.frames.length; i++) {
window.frames[i].postMessage(JSON.stringify({ method: 'pause' }), '*') //Pauses Vimeo Video
$('#AVRS-Carousel').carousel('cycle'); //Starts Carousel
$('body').foggy(false); //Disables Foggy.js
}
});
$('[data-frame-index]').on('click', function (event) {
var $span = $(event.currentTarget),
index = $span.data('frame-index');
window.frames[index].postMessage(JSON.stringify({ method: 'play' }), '*') //Auto Plays Vimeo
$('#AVRS-Carousel').carousel('pause'); //Pauses Carousel
$('body').foggy(); //Applies Foggy.js
});
});
Also here is CSS for Bootstraps back-drop class for the Modal:
.modal-backdrop {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: #zindex-modal-background;
background-color: #modal-backdrop-bg;
// Fade for backdrop
&.fade { .opacity(0); }
&.in { .opacity(#modal-backdrop-opacity); }
filter: blur(5px)
}
Thank you for your time in advance.
I have found no way to remove the blur effect from child elements. In the end I applied the blur to nav and section elements and kept modals outside of them. Bootstraps documentation for modals also warns about markup placement, saying:
Always try to place a modal's HTML code in a top-level position in your document to avoid other components affecting the modal's appearance and/or functionality.
So the HTML ends up looking something like this:
<body>
<nav>...</nav>
<div class="modal">...</div>
<section>...</section>
</body>
Javascript (using bootstraps data-toggle attribute on a button):
$('button[data-toggle]').on('click', function (event) {
$('nav, section').foggy();
});
I hope this was somewhat helpful.

dynamic creation of HTML elements

I am trying to create a coverflow using the plugin locate here: http://vanderlee.github.io/coverflow/
All I have to do is this:
<div class="coverflow">
<div class="cover">A</div>
<div class="cover">B</div>
...
<div class="cover">Y</div>
<div class="cover">Z</div>
</div>
<script>
$(function() {
$('.coverflow').coverflow();
});
</script>
This works fine for manually created elements but when doing something like the following:
var el = {
newDiv: $("<div>", { class: "cover newDiv"}),
newElt: $("<div>", { class: "cover newDiv"})
}
$("#preview-coverflow").append(el.newDiv);
$("#preview-coverflow").append(el.newElt);
my divs are displayed in a vertical fashion, rather than horizontal. My question is whether there is any difference between the two forms of element creation? In both cases I am (trying) to create empty div and append to the parent. Am I doing anything wrong?
In my style sheet, just to see my empty divs:
.newDiv {
background-color:red
}
Try using the refresh method after you add new elements
$('.coverflow').coverflow('refresh');
From Docs:
refresh
Redraw the covers. You shouldn't ever need to do this unless you are adding or removing
covers or changing the covers yourself.
It looks like the .coverflow() needs to be run after the elements have been added.
In the console, after dynamically adding the html, just try running
$('.coverflow').coverflow('refresh');
If that seems to fix the issue, then look at implementing it after you add the new elements.
Didnt notice the plugin has a refresh function to it, using the refresh should solve it.
Here you go:
<style>
.newDiv {
background: gold;
position: absolute;
width: 320px;
height: 320px;
overflow:hidden;
}
</style>
<script>
$(function() {
$('.coverflow').coverflow();
addNewTest();// test call here
});
function addNewTest(){
var el = {
newDiv: $("<div>", { class: "cover newDiv"}),
newElt: $("<div>", { class: "cover newDiv"})
}
$("#preview-coverflow").append(el.newDiv, el.newElt).coverflow('refresh');
}
</script>
try this without any plugin for dynamic creation of elements. Its pure javascript and JQuery:
<script>
var myDivs = "";
for(var i=0; i <10; i++)
{
myDivs+= "<div class='cover'></div>";
}
$(".coverflow").html(myDivs);
<script>
I think it clear your concept. Hope this works.

How to use javascript to hide and let divs reappear

I am trying to make webpage where there is a div in the center which is being changed, instead of going to different pages.
Ultimately, I would like to have the new div, when clicking on an arrow, to flow from right or left in to the center. But first I would like to make the divs appear and disappear when clicking on the arrows but unfortunately this doesn't work.
This is my javascript:
<script>
function changeToHome() {
document.getElementById("mainmain").style.display="block";
document.getElementById("mainmain2").style.display="none";
document.getElementById("mainmain3").style.display="none";
document.getElementById("mainmain4").style.display="none";
}
function changeToStudy() {
document.getElementById("mainmain").style.display="none";
document.getElementById("mainmain2").style.display="block";
document.getElementById("mainmain3").style.display="none";
document.getElementById("mainmain4").style.display="none";
}
function changeToJob() {
document.getElementById("mainmain").style.display="none";
document.getElementById("mainmain2").style.display="none";
document.getElementById("mainmain3").style.display="block";
document.getElementById("mainmain4").style.display="none";
}
function changeToContact() {
document.getElementById("mainmain").style.display="none";
document.getElementById("mainmain2").style.display="none";
document.getElementById("mainmain3").style.display="none";
document.getElementById("mainmain4").style.display="block";
}
function changePageRight() {
var displayValue5 = document.getElementById('mainmain').style.display;
var displayValue5 = document.getElementById('mainmain2').style.display;
var displayValue6 = document.getElementById('mainmain3').style.display;
var displayValue7 = document.getElementById('mainmain4').style.display;
if (document.getElementById('mainmain').style.display == "block") {
document.getElementById("mainmain").style.display="none";
document.getElementById("mainmain2").style.display="block";
}
else if (document.getElementById('mainmain2').style.display == "block") {
document.getElementById("mainmain2").style.display="none";
document.getElementById("mainmain3").style.display="block";
}
else if (document.getElementById('mainmain3').style.display == "block") {
document.getElementById("mainmain3").style.display="none";
document.getElementById("mainmain4").style.display="block";
}
else if (displayValue8 == block) {}
}
function changePageLeft() {
var displayValue = document.getElementById('mainmain').style.display;
var displayValue2 = document.getElementById('mainmain2').style.display;
var displayValue3 = document.getElementById('mainmain3').style.display;
var displayValue4 = document.getElementById('mainmain4').style.display;
if (displayValue == "block") { }
else if (displayValue2 == "block") {
document.getElementById("mainmain").style.display="block";
document.getElementById("mainmain2").style.display="none";
}
else if (displayValue3 == "block") {
document.getElementById("mainmain2").style.display="block";
document.getElementById("mainmain3").style.display="none";
}
else if (displayValue4 === "block") {
document.getElementById("mainmain3").style.display="block";
document.getElementById("mainmain4").style.display="none";
}
}
</script>
Now I have a few divs that look like this:
<div id="mainmain4">
<img style="width:400px;height:327px;margin-left:auto;margin-right:auto;display:block;" src="Untitled-22.png" />
<h2> My name </h2>
<p>Hi,</p>
<p>Some text</p>
</div>
With these css atributes:
#mainmain {
float: left;
width: 575px;
display: block;
position: relative;
}
And all other divs with display: none; so I can change this to block and the one that was block to none.
For some reason, after when I click on one button of the menu, which activates a changeToX() function, the arrows work great. But before that, when you first go to the website, it doesn't.
Can someone explain me what I do wrong?
You don't tell the browser which divs shall be displayed on load. You can use theonloadevent for this:
<body onload="changeToHome()">
One additional hint: you maybe don't want to use inline JavaScript and CSS.
jQuery is as this simple:
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
toggle!
<div id="mainmain">test text</div>
<script>
// you need this, only apply javascript when all html (dom) is loaded:
$(document).ready(function() {
$('.toggle-container').on('click', function(e) {
e.preventDefault(); // this prevents the real href to '#'
// .toggle() is like "on / off" switch for hiding and showing a container
$($(this).data('container')).toggle();
});
});
</script>
This function can be reused, because it is based on classes instead of id's.
Check this JSFiddle: http://jsfiddle.net/r8L6xg15/
Maybe this is of some use. I've tried to make a page control-like behaviour. You can select any container div and put elements in there that have the class 'page'. The JavaScript code will let you navigate those with buttons.
You can make it more fancy by adding the buttons through JavaScript. What you then have is basically a list of pages which are normally displayed as regular divs, but when the script kicks in, it changes them to a page control.
You can call this for any parent element, and in that sense it behaves a bit like a jQuery plugin. It is all native JavaScript, though. And not too much code, I hope. Like you said, I think it's good to learn JavaScript at first. It is very powerful by itself, and it's becoming increasingly powerful. jQuery adds a lot of convenience functions and provides fallbacks in case browser don't support certain features, or when implementations differ. But for many tasks, bare JavaScript will do just fine, and it certainly can't hurt to know your way around it.
Press the 'Run this snippet' button at the bottom to see it in action.
function Pages(element)
{
// Some initialization
var activePage;
// Find all pages within this element.
var pages = document.querySelectorAll('.page');
var maxPage = pages.length - 1;
// Function to toggle the active page.
var setPage = function(index)
{
activePage = index;
for (p = 0; p <= maxPage; p++)
{
if (p == activePage)
pages[p].className = 'page active';
else
pages[p].className = 'page inactive';
}
}
// Select the first page by default.
setPage(0);
// Handler for 'previous'
element.querySelector('.prev').onclick = function()
{
if (activePage == 0)
return;
setPage(activePage - 1);
}
// Handler for 'next'
element.querySelector('.next').onclick = function()
{
if (activePage == maxPage)
return;
setPage(activePage + 1);
}
// Add a class to the element itself. This way, you can already change CSS styling
// depending on whether this code is loaded or not. So in case of an error, the
// divs are just all show underneath each other, and the nav buttons are hidden.
element.className = element.className + ' js';
}
Pages(document.querySelector('.pages'));
.pages .page {
display: block;
padding: 40px;
border: 1px solid blue;
}
.pages .page.inactive {
display: none;
}
.pages .nav {
display: none;
}
.pages.js .nav {
display: inline-block;
}
<div class="pages">
<button class="nav prev">Last</button>
<button class="nav next">Next</button>
<div class="page">Page 1 - Introduction and other blah</div>
<div class="page">Page 2 - Who am I? Who are you? Who is Dr Who?</div>
<div class="page">Page 3 - Overview of our products
<ul><li>Foo</li><li>Bar</li><li>Bar Pro</li></ul>
</div>
<div class="page">Page 4 - FAQ</div>
<div class="page">Page 5 - Contact information</div>
</div>
To dos to make this a little more professional:
Add the navigation through JavaScript
Disable the buttons when first/last page has been reached
Support navigation by keys too (or even swipe!)
Some CSS transform (fade or moving) when toggling between pages
Smarter adding and removing of classes. Now I just set className, which sucks if someone would like to add classes themselves. jQuery has addClass and removeClass for this, which is helpful. there are also stand-alone libraries that help you with this.
Visible indication of pages, maybe with tabs at the top?

jScrollPane (jQuery) scrolling all items in a content pane API at the same time

I need some help with something. I have 3 or 4 jScrollPanes on the same page, something like this creates them:
#foreach (var thing in Model.things)
{
<div class="scrollPanes">
<ul>
<li>Model.Item</li>
</ul>
</div>
}
My CSS is like (these panes scroll horizontally):
.scrollPanes
{
overflow: auto;
width: 100%;
}
ul
{
list-style: none;
height: 50px;
width: 1000px;
margin: 0;
padding: 0;
}
And here is how i initialize the jScrollPane
var settings = { hideFocus: true };
// Notice how all the elements with class "scrollPane" become a jscrollpane
var pane = $('.scrollPanes').jScrollPane(settings);
var api = pane.data('jsp');
api.reinitialise();
So this makes every a scrollPane, and they all work perfectly fine as I'd expect them to.
What I WANT to do is scroll ALL of the elements by an equal increment using jScrollPane().scrollToX(xxx); or jScrollPane().scrollByX(xxx);
When I call the method:
api.scrollByX(200); api.reinitialise();
That works, but it only scrolls the first list, not any after.
I'm seeking to get it to scroll all of them equally, I'd really appreciate any help on this.
Thanks
The reason your original code didn't work is because of how the .data() method works. See http://api.jquery.com/data/: "Returns value at named data store for the first element in the jQuery collection..." When you call pane.data('jsp'), it's only returning the API for the first element in the collection (which is why only the first pane scrolls for you). To run a method on the API for every element in the collection, you'll need to loop through the elements one by one. You can do that by using .each()
$(".jpanes").each(function() {
$(this).data("jsp").scrollByX(200);
});
Or, if you plan to call the APIs repeatedly, you can get an array of the APIs to call whenever you need and then use a for loop.
// Store an array of APIs for each element
var apis = $(".jpanes").map(function() {
return $(this).data("jsp");
}).get();
// Call an API method for each element
for (var i = 0, api; api = apis[i]; i++) {
api.scrollByX(200);
}
1) Add to each pane individual id
2) Write own function PanesMover(X) for changing all panes at once.
In the function move each pane on same X (recived as function argument), separetly by it's ID
3) Call in your document PanesMover(X), where X -is parameter of moving, which would be applied to all panes.
Here's what I did in simplified form, following Answer #1.
First, I attached unique IDs on all elements I wanted to scroll simultaneously, but they all use the same class.
#{ int paneID = 0; }
#foreach (var thing in Model.things)
{
<div id="#paneID" class="jpanes">
<ul>
<li>Item</li>
</ul>
</div>
paneID++;
}
Then I initialized the panes with jScrollPane as before:
$('.jpanes').jScrollPane();
Then I initialized a separate jScrollPane API for each element's unique ID using an array, something like this:
var jspAPI = new Array();
$('.jpanes').each(function(index) {
jspAPI[index] = $('#'+$(this).attr('id')).data('jsp');
}
That's it then, when I wanted to scroll them all I iterated through all in a loop
for (var i=0; i < jspAPI.length; i++) {
jspAPI[i].scrollByX(10);
}
I'm surprised I couldn't find anybody who has wanted to do this before and asked for help, but hopefully this helps someone else.

Scroll to bottom of div?

I am creating a chat using Ajax requests and I'm trying to get messages div to scroll to the bottom without much luck.
I am wrapping everything in this div:
#scroll {
height:400px;
overflow:scroll;
}
Is there a way to keep it scrolled to the bottom by default using JS?
Is there a way to keep it scrolled to the bottom after an ajax request?
Here's what I use on my site:
var objDiv = document.getElementById("your_div");
objDiv.scrollTop = objDiv.scrollHeight;
This is much easier if you're using jQuery scrollTop:
$("#mydiv").scrollTop($("#mydiv")[0].scrollHeight);
Try the code below:
const scrollToBottom = (id) => {
const element = document.getElementById(id);
element.scrollTop = element.scrollHeight;
}
You can also use Jquery to make the scroll smooth:
const scrollSmoothlyToBottom = (id) => {
const element = $(`#${id}`);
element.animate({
scrollTop: element.prop("scrollHeight")
}, 500);
}
Here is the demo
Here's how it works:
Ref: scrollTop, scrollHeight, clientHeight
using jQuery animate:
$('#DebugContainer').stop().animate({
scrollTop: $('#DebugContainer')[0].scrollHeight
}, 800);
Newer method that works on all current browsers:
this.scrollIntoView(false);
var mydiv = $("#scroll");
mydiv.scrollTop(mydiv.prop("scrollHeight"));
Works from jQuery 1.6
https://api.jquery.com/scrollTop/
http://api.jquery.com/prop/
alternative solution
function scrollToBottom(element) {
element.scroll({ top: element.scrollHeight, behavior: 'smooth' });
}
smooth scroll with Javascript:
document.getElementById('messages').scrollIntoView({ behavior: 'smooth', block: 'end' });
If you don't want to rely on scrollHeight, the following code helps:
$('#scroll').scrollTop(1000000);
Java Script:
document.getElementById('messages').scrollIntoView(false);
Scrolls to the last line of the content present.
My Scenario: I had an list of string, in which I had to append a string given by a user and scroll to the end of the list automatically. I had fixed height of the display of the list, after which it should overflow.
I tried #Jeremy Ruten's answer, it worked, but it was scrolling to the (n-1)th element. If anybody is facing this type of issue, you can use setTimeOut() method workaround. You need to modify the code to below:
setTimeout(() => {
var objDiv = document.getElementById('div_id');
objDiv.scrollTop = objDiv.scrollHeight
}, 0)
Here is the StcakBlitz link I have created which shows the problem and its solution : https://stackblitz.com/edit/angular-ivy-x9esw8
If your project targets modern browsers, you can now use CSS Scroll Snap to control the scrolling behavior, such as keeping any dynamically generated element at the bottom.
.wrapper > div {
background-color: white;
border-radius: 5px;
padding: 5px 10px;
text-align: center;
font-family: system-ui, sans-serif;
}
.wrapper {
display: flex;
padding: 5px;
background-color: #ccc;
border-radius: 5px;
flex-direction: column;
gap: 5px;
margin: 10px;
max-height: 150px;
/* Control snap from here */
overflow-y: auto;
overscroll-behavior-y: contain;
scroll-snap-type: y mandatory;
}
.wrapper > div:last-child {
scroll-snap-align: start;
}
<div class="wrapper">
<div>01</div>
<div>02</div>
<div>03</div>
<div>04</div>
<div>05</div>
<div>06</div>
<div>07</div>
<div>08</div>
<div>09</div>
<div>10</div>
</div>
You can use the HTML DOM scrollIntoView Method like this:
var element = document.getElementById("scroll");
element.scrollIntoView();
Javascript or jquery:
var scroll = document.getElementById('messages');
scroll.scrollTop = scroll.scrollHeight;
scroll.animate({scrollTop: scroll.scrollHeight});
Css:
.messages
{
height: 100%;
overflow: auto;
}
Using jQuery, scrollTop is used to set the vertical position of scollbar for any given element. there is also a nice jquery scrollTo plugin used to scroll with animation and different options (demos)
var myDiv = $("#div_id").get(0);
myDiv.scrollTop = myDiv.scrollHeight;
if you want to use jQuery's animate method to add animation while scrolling down, check the following snippet:
var myDiv = $("#div_id").get(0);
myDiv.animate({
scrollTop: myDiv.scrollHeight
}, 500);
I have encountered the same problem, but with an additional constraint: I had no control over the code that appended new elements to the scroll container. None of the examples I found here allowed me to do just that. Here is the solution I ended up with .
It uses Mutation Observers (https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) which makes it usable only on modern browsers (though polyfills exist)
So basically the code does just that :
var scrollContainer = document.getElementById("myId");
// Define the Mutation Observer
var observer = new MutationObserver(function(mutations) {
// Compute sum of the heights of added Nodes
var newNodesHeight = mutations.reduce(function(sum, mutation) {
return sum + [].slice.call(mutation.addedNodes)
.map(function (node) { return node.scrollHeight || 0; })
.reduce(function(sum, height) {return sum + height});
}, 0);
// Scroll to bottom if it was already scrolled to bottom
if (scrollContainer.clientHeight + scrollContainer.scrollTop + newNodesHeight + 10 >= scrollContainer.scrollHeight) {
scrollContainer.scrollTop = scrollContainer.scrollHeight;
}
});
// Observe the DOM Element
observer.observe(scrollContainer, {childList: true});
I made a fiddle to demonstrate the concept :
https://jsfiddle.net/j17r4bnk/
Found this really helpful, thank you.
For the Angular 1.X folks out there:
angular.module('myApp').controller('myController', ['$scope', '$document',
function($scope, $document) {
var overflowScrollElement = $document[0].getElementById('your_overflow_scroll_div');
overflowScrollElement[0].scrollTop = overflowScrollElement[0].scrollHeight;
}
]);
Just because the wrapping in jQuery elements versus HTML DOM elements gets a little confusing with angular.
Also for a chat application, I found making this assignment after your chats were loaded to be useful, you also might need to slap on short timeout as well.
Like you, I'm building a chat app and want the most recent message to scroll into view. This ultimately worked well for me:
//get the div that contains all the messages
let div = document.getElementById('message-container');
//make the last element (a message) to scroll into view, smoothly!
div.lastElementChild.scrollIntoView({ behavior: 'smooth' });
small addendum: scrolls only, if last line is already visible. if scrolled a tiny bit, leaves the content where it is (attention: not tested with different font sizes. this may need some adjustments inside ">= comparison"):
var objDiv = document.getElementById(id);
var doScroll=objDiv.scrollTop>=(objDiv.scrollHeight-objDiv.clientHeight);
// add new content to div
$('#' + id ).append("new line at end<br>"); // this is jquery!
// doScroll is true, if we the bottom line is already visible
if( doScroll) objDiv.scrollTop = objDiv.scrollHeight;
Just as a bonus snippet. I'm using angular and was trying to scroll a message thread to the bottom when a user selected different conversations with users. In order to make sure that the scroll works after the new data had been loaded into the div with the ng-repeat for messages, just wrap the scroll snippet in a timeout.
$timeout(function(){
var messageThread = document.getElementById('message-thread-div-id');
messageThread.scrollTop = messageThread.scrollHeight;
},0)
That will make sure that the scroll event is fired after the data has been inserted into the DOM.
This will let you scroll all the way down regards the document height
$('html, body').animate({scrollTop:$(document).height()}, 1000);
You can also, using jQuery, attach an animation to html,body of the document via:
$("html,body").animate({scrollTop:$("#div-id")[0].offsetTop}, 1000);
which will result in a smooth scroll to the top of the div with id "div-id".
Scroll to the last element inside the div:
myDiv.scrollTop = myDiv.lastChild.offsetTop
You can use the Element.scrollTo() method.
It can be animated using the built-in browser/OS animation, so it's super smooth.
function scrollToBottom() {
const scrollContainer = document.getElementById('container');
scrollContainer.scrollTo({
top: scrollContainer.scrollHeight,
left: 0,
behavior: 'smooth'
});
}
// initialize dummy content
const scrollContainer = document.getElementById('container');
const numCards = 100;
let contentInnerHtml = '';
for (let i=0; i<numCards; i++) {
contentInnerHtml += `<div class="card mb-2"><div class="card-body">Card ${i + 1}</div></div>`;
}
scrollContainer.innerHTML = contentInnerHtml;
.overflow-y-scroll {
overflow-y: scroll;
}
<link href="https://cdn.jsdelivr.net/npm/bootstrap#4.5.3/dist/css/bootstrap.min.css" rel="stylesheet"/>
<div class="d-flex flex-column vh-100">
<div id="container" class="overflow-y-scroll flex-grow-1"></div>
<div>
<button class="btn btn-primary" onclick="scrollToBottom()">Scroll to bottom</button>
</div>
</div>
Css only:
.scroll-container {
overflow-anchor: none;
}
Makes it so the scroll bar doesn't stay anchored to the top when a child element is added. For example, when new message is added at the bottom of chat, scroll chat to new message.
Why not use simple CSS to do this?
The trick is to use display: flex; and flex-direction: column-reverse;
Here is a working example. https://codepen.io/jimbol/pen/YVJzBg
A very simple method to this is to set the scroll to to the height of the div.
var myDiv = document.getElementById("myDiv");
window.scrollTo(0, myDiv.innerHeight);
On my Angular 6 application I just did this:
postMessage() {
// post functions here
let history = document.getElementById('history')
let interval
interval = setInterval(function() {
history.scrollTop = history.scrollHeight
clearInterval(interval)
}, 1)
}
The clearInterval(interval) function will stop the timer to allow manual scroll top / bottom.
I know this is an old question, but none of these solutions worked out for me. I ended up using offset().top to get the desired results. Here's what I used to gently scroll the screen down to the last message in my chat application:
$("#html, body").stop().animate({
scrollTop: $("#last-message").offset().top
}, 2000);
I hope this helps someone else.
I use the difference between the Y coordinate of the first item div and the Y coordinate of the selected item div. Here is the JavaScript/JQuery code and the html:
function scrollTo(event){
// In my proof of concept, I had a few <button>s with value
// attributes containing strings with id selector expressions
// like "#item1".
let selectItem = $($(event.target).attr('value'));
let selectedDivTop = selectItem.offset().top;
let scrollingDiv = selectItem.parent();
let firstItem = scrollingDiv.children('div').first();
let firstItemTop = firstItem.offset().top;
let newScrollValue = selectedDivTop - firstItemTop;
scrollingDiv.scrollTop(newScrollValue);
}
<div id="scrolling" style="height: 2rem; overflow-y: scroll">
<div id="item1">One</div>
<div id="item2">Two</div>
<div id="item3">Three</div>
<div id="item4">Four</div>
<div id="item5">Five</div>
</div>

Categories