Attach and detach affixed headers with Twitter Bootstrap - javascript

I'm trying to use the affix function to attach a header to the top of the screen, but have it attached only for a portion of the page. It should detach (and scroll up along with the content) when the user scrolls past a certain point.
I'm using the script from this jsfiddle.
What I'm trying right now is this:
$('#nav-wrapper').height($("#nav").height());
$('#nav').affix({
offset: $('#nav').position()
});
$('#nav').detached({
offset: $('#bottom').position()
});
With the .detached class like so:
.detached { position: static; }
Can't get this to work. Any suggestions?

Twitter Bootstrap affix module doesn't have that option. But, I've used many times hcSticky, it is awesome. Take a look, it's simply to use and works very well.

You can write the logic in a function, and pass it to affix as offset.top.
Try
var navHeight = $("#nav").height();
var detachTop = $("#detach").offset().top;
var navTop = $("#nav-wrapper").offset().top;
$('#nav-wrapper').height(navHeight);
$('#nav').affix({
offset : {
top : function() {
if ((navHeight + $(window).scrollTop()) > detachTop) {
return Number.MAX_VALUE;
}
return navTop;
}
}
});
Fiddle is here.

Another option which might work for you: http://jsfiddle.net/panchroma/5n9vw/
HTML
<div class="header" data-spy="affix">
affixed header, released after scrolling 100px
</div>
Javascript
$(document).ready(function(){
$(window).scroll(function(){
var y = $(window).scrollTop();
if( y > 100 ){
$(".header.affix").css({'position':'static'});
} else {
$(".header.affix").css({'position':'fixed'});
}
});
})
Good luck!

Related

Add class onscroll combined with onepage-scroll.js plug-in

I'm currently using the onepage-scroll.js (https://github.com/peachananr/onepage-scroll) plug-in on my website to scroll through the homepage. When scrolling past the first "slide" I would also like to add a class (sticky) to my header to change some CSS. I've tried the code below, but I can't seem to get it working and I'm kinda in the dark here on how to make this solution work.
var header = $("header");
$("#sliders").scroll(function() {
var scroll = $('#sliders').scrollTop();
console.log(scroll);
if (scroll >= 50) {
header.addClass("sticky");
} else {
header.removeClass("sticky");
}
});
Try to make it on document ready.
Down only my example worked code on onepage-scroll.js
$(document).ready(function(){
$(".main").onepage_scroll({
sectionContainer: ".sectionscroll",
responsiveFallback: 600,
loop: true,
afterMove:function (index){
if ((index == 2)||(index == 3)){
$('#main').addClass('darktheme');
}else{
$('#main').removeClass('darktheme');
}
}
});
//$(".main").moveTo(2);
$(".btn-list-bottom").click(function(){$(".main").moveTo(4)});
});
All you section must have the same class.

On scroll, logo color changes, but scrolling back up it stays the same

Im creating a fixed header where on load, the logo is flat white. On scroll, it changes to the full color logo.
However, when scrolling back to the top, it stays the same colored logo instead of going back to white.
Here's the code (and a pen)
$(function() {
$(window).scroll(function() {
var navlogo = $('.nav-logo-before');
var scroll = $(window).scrollTop();
if (scroll >= 1) {
navlogo.removeClass('.nav-logo-before').addClass('nav-logo-after');
} else {
navlogo.removeClass('.nav-logo-after').addClass('nav-logo-before');
}
});
});
http://codepen.io/bradpaulp/pen/gmXOjG
There's a couple of things here:
1) You start with a .nav-logo-before class but when the logo becomes black you remove that class and then try to get the same element using a class selector that doesn't exist anymore
2) removeClass('.nav-logo-before') is different than removeClass('nev-logo-before), notice the "." in the first selector.
3) You get the element using the $('.selector')in every scroll event, this can be a performance issue, it's better to cache them on page load and then use the element stored in memory
4) It's not a good practice to listen to scroll events as this can be too performance demanding, it's usually better to use the requestAnimationFrame and then check if the scroll position has changed. Using the scroll event it could happen that you scroll up really fast and the scroll event doesn't happen at 0, so your logo won't change. With requestAnimationFrame this can't happen
$(function() {
var navlogo = $('.nav-logo');
var $window = $(window);
var oldScroll = 0;
function loop() {
var scroll = $window.scrollTop();
if (oldScroll != scroll) {
oldScroll = scroll;
if (scroll >= 1) {
navlogo.removeClass('nav-logo-before').addClass('nav-logo-after');
} else {
navlogo.removeClass('nav-logo-after').addClass('nav-logo-before');
}
}
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
});
body {
background-color: rgba(0,0,0,.2);
}
.space {
padding: 300px;
}
.nav-logo-before {
content: url(https://image.ibb.co/kYANyv/logo_test_before.png)
}
.nav-logo-after {
content: url(https://image.ibb.co/jYzFJv/logo_test_after.png)
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<img class="nav-logo nav-logo-before">
</div>
<div class="space">
</div>
Dont need to add the dot . in front of the class name in removeClass and addClass:
Use this:
navlogo.removeClass('nav-logo-before')
Secondly, you are removing the class that you are using to get the element in the first place.
I have an updated codepen, see if this suits your needs: http://codepen.io/anon/pen/ZeaYRO
You are removing the class nav-logo-before, so the second time the function runs, it can't find any element with nav-logo-before.
Just give a second class to your navlogo element and use that on line 3.
Like this:
var navlogo = $('.second-class');
working example:
http://codepen.io/anon/pen/ryYajx
You are getting the navlogo variable using
var navlogo = $('.nav-logo-before');
but then you change the class to be 'nav-logo-after', so next time the function gets called you won't be able to select the logo using jquery as it won't have the '.nav-logo-before'class anymore.
You could add an id to the logo and use that to select it, for example.
Apart from that, removeClass('.nav-logo-before') should be removeClass('nav-logo-before') without the dot before the class name.
The problem is that you removes nav-logo-before and then you want to select element with such class but it doesn't exist.
I've rafactored you code to avert it.
Another problem is that you uses dot in removeClass('.before') while it should be removeClass('before') - without dot
$(function() {
var navlogo = $('.nav-logo');
$(window).scroll(function() {
var scroll = $(window).scrollTop();
if (scroll >= 1) {
navlogo.removeClass('before').addClass('after');
} else {
navlogo.removeClass('after').addClass('before');
}
});
});
body {
background-color: rgba(0,0,0,.2);
}
.space {
padding: 300px;
}
.before {
content: url(https://image.ibb.co/kYANyv/logo_test_before.png)
}
.after {
content: url(https://image.ibb.co/jYzFJv/logo_test_after.png)
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<img class="nav-logo before">
</div>
<div class="space">
</div>

Affix is not working in Bootstrap 4 (alpha)

According to Bootstrap 3 docs I have added following attributes to a navbar:
<nav class="navbar no-margin-bottom" data-spy="affix" data-offset-top="90" >
...
</nav>
After scrolling down the page Bootstrap 4 is not adding class to navbar which is affix. Can anyone tell me how to solve this problem? Bootstrap.js and jQuery.js are working.
Although the affix is removed from Bootstrap in version 4. However, you can achieve your goal through this jQuery Code:
$(window).on('scroll', function(event) {
var scrollValue = $(window).scrollTop();
if (scrollValue == settings.scrollTopPx || scrollValue > 70) {
$('.navbar').addClass('fixed-top');
}
});
Update Bootstrap 4
The docs recommend the sticky polyfill, and the recommended ScrollPos-Styler doesn't really help with scroll position (you can easily define an offset).
I think it's easier to use jQuery to watch the window scroll and change the CSS accordingly to fixed...
var toggleAffix = function(affixElement, wrapper, scrollElement) {
var height = affixElement.outerHeight(),
top = wrapper.offset().top;
if (scrollElement.scrollTop() >= top){
wrapper.height(height);
affixElement.addClass("affix");
}
else {
affixElement.removeClass("affix");
wrapper.height('auto');
}
};
$('[data-toggle="affix"]').each(function() {
var ele = $(this),
wrapper = $('<div></div>');
ele.before(wrapper);
$(window).on('scroll resize', function() {
toggleAffix(ele, wrapper, $(this));
});
// init
toggleAffix(ele, wrapper, $(window));
});
Bootstrap 4 affix (sticky navbar)
EDIT: Another solution is to use this port of the 3.x Affix plugin as a replacement in Bootstrap 4..
http://www.codeply.com/go/HmY7DLHLkI
Related: Animate/Shrink NavBar on scroll using Bootstrap 4
From the bootstrap v4 documentation:
Dropped the Affix jQuery plugin. We recommend using a position: sticky polyfill instead. See the HTML5 Please entry for details and specific polyfill recommendations.
If you were using Affix to apply additional, non-position styles, the polyfills might not support your use case. One option for such uses is the third-party ScrollPos-Styler library.
To build on Anwar Hussain's answer. I found success with this:
$(window).on('scroll', function (event) {
var scrollValue = $(window).scrollTop();
if (scrollValue > 120) {
$('.navbar').addClass('affix');
} else{
$('.navbar').removeClass('affix');
}
});
This will apply the class to the navbar when scrolling down, but will also remove the class when scrolling back up. Previously, when scrolling back up, the applied class would stay applied to the navbar.
As per Vucko's quote within the Mirgation docs, ScrollPos-Styler library suited me quite well.
Include the .js ScrollPos-Styler (scrollPosStyler.js) file to your page.
Find the relevant <div> you wish to make 'sticky'
<nav class="navbar navbar-toggleable-md">
Apply sps sps--abv class
<nav class="navbar navbar-toggleable-md sps sps--abv">
Add .css styles you wish to have triggered once the element has become sticky (As per the demo page.
/**
* 'Shared Styles'
**/
.sps {
}
/**
* 'Sticky Above'
*
* Styles you wish to apply
* Once stick has not yet been applied
**/
.sps--abv {
padding: 10px
}
/**
* 'Sticky Below'
*
* Styles you wish to apply
* Once stick has been applied
**/
.sps--blw {
padding: 2px
}
$(window).on('scroll', function (event) {
var scrollValue = $(window).scrollTop();
var offset = $('[data-spy="affix"]').attr('data-offset-top');
if (scrollValue > offset) {
$('[data-spy="affix"]').addClass('affix-top');
var width = $('[data-spy="affix"]').parent().width();
$('.affix-top').css('width', width);
} else{
$('[data-spy="affix"]').removeClass('affix-top');
}
});
For the users who are looking for an answer in pure Javascript, this is how you can do it by using pure Javascript:
window.onscroll = (e) => {
let scrollValue = window.scrollY;
if (scrollValue > 120) {
document.querySelector('.navbar').classList.add('affix');
} else{
document.querySelector('.navbar').classList.remove('affix');
}
}
With bootstrap 4 there is special class for that.
Try removing the data-spy="affix" data-offset-top="90" attributes, and just add 'sticky-top'.
documentation
After trying every solution I could find (and being aware that affix was removed from bootstrap 4), the only way I could get the desired sticky results I needed was to use sticky-kit.
This is s really simple jQuery plugin that was easy to set-up.
Just include the code in your project and then assign the element you want to stick with
$("#sidebar").stick_in_parent();
simple use class fixed-top in bootstrap 4 and use addClass and removeClass
$(window).on('scroll', function(event) {
var scrollValue = $(window).scrollTop();
if ( scrollValue > 70) {
$('.navbar-sticky').addClass('fixed-top');
}else{
$('.navbar-sticky').removeClass('fixed-top');
}
});

How can I hide a button when scrolled to the top of a page?

I'm trying to adapt this JSFiddle to make the menu button on my website hide when I'm at the top of the page and show when I start scrolling down.
I modified the JS to match the CSS on my site. Then I placed it in tags in the head of my page
var $scb = $('<div class="toggle-menu-wrap"></div>');
$('.top-header').append($scb);
var $ccol = $('.content');
$ccol.scroll(function(){
$scb.stop(true,true).fadeTo(500, $ccol.scrollTop() > 10 ? 1 : 0);
});
However, it still doesn't work. Am I making a mistake in how I'm modifying the JS to fit my CSS?
You can include the toggle-menu-wrap element in your HTML from the start. There is no need to insert it using JS.
Write the one line of CSS you need, which is to hide the element from the beginning
.toggle-menu-wrap {
display: none;
}
Your version of jQuery uses 'jQuery' instead of '$' to reference itself. I would also re-write your JS like:
jQuery(document).ready(function() {
fadeMenuWrap();
jQuery(window).scroll(fadeMenuWrap);
});
function fadeMenuWrap() {
var scrollPos = window.pageYOffset || document.documentElement.scrollTop;
if (scrollPos > 300) {
jQuery('.toggle-menu-wrap').fadeIn(300);
} else {
jQuery('.toggle-menu-wrap').fadeOut(300);
}
}
Like #murli2308 said in the comments above, you need to attach a scroll event listener to the window:
$(document).ready(function () {
var $scb = $('<div class="scroll-border"></div>');
$('.above').append($scb);
var $ccol = $('.content');
$(window).scroll(function(){
$scb.stop(true,true).fadeTo(500, $ccol.scrollTop() > 10 ? 1 : 0);
});
})
Wrapping your code in $(document).ready() would also be a good idea.
The reason $ccol.scroll(function() { ... works in that fiddle is because of the CSS:
.content{
height: 200px;
width: 200px;
overflow: auto;
}
Notice overflow: auto;. This causes that specific div to be scrollable. However, on your website, you scroll the entire page, not $ccol. This means the event handler will never fire a scroll event (since $ccol will never scroll).
You might have forgotten to link Jquery.
<script type="text/javascript" src="http://code.jquery.com/jquery-1.8.3.min.js"></script>
Link this inside your head tag incase.....
This should do the job:
$(window).scroll(function(e){
if ($(this).scrollTop() > 0) {
$(".your_element").css("display", "block");
} else {
$(".your_element").css("display", "none");
}
});

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