I have this page (https://www.datacoral.com/architecture/) that has a left sidebar with five bullet points. What I want to happen is as the user scrolls past these five div's on the right hand column, the text in one of these bullet points adds a class called 'bolder' and the text become a font-weight of 700 to let the user know what point they are in on the page. As the pass by that same div, the class disappears and ends up in the next bullet point since you're now passing by another div.
I've got it partially working but it's not hitting the right point of the div at all. Seems to add the class as you are passing the bottom of the div instead of the top.
This is the code I'm currently working with. Anyone know what I might be doing wrong so this can function properly?
Note: Should mention I'm basically duplicating this code five times and just swapping out the numbers.
jQuery(function(){
jQuery(window).scroll(function() {
var scroll = jQuery(window).scrollTop(); // how many pixels you've scrolled
var os = jQuery('#guide_body-0').offset().top; // pixels to the top of div1
var ht = jQuery('#guide_body-0').height(); // height of guide_body-0 in pixels
if(scroll > os + ht){
jQuery('.scroll-nav-0').addClass('bolder');
}
});
});
I think firing a function on scroll like that gets a little bit crazy. I always delay the function until the scrolling has stopped.
As far as the catch point, i think your code is applying the classes when the element has moved out of view. i would use the bottom of the browser screen as a reference point.
Think about it like this:
$(window).scrollTop() returns 0 at the top of the page.
$('#guide_body-0').offset().top returns 1000px.
So $(window).scrollTop() is equal to $('#guide_body-0').offset().top when that element is at the top of the screen.
Add $('#guide_body-0').height() to the equation and that puts the scroll position (the top of the screen) at the bottom of the element.
What you need to do is check if the offset.top property of the element is in a scroll position which puts it above the bottom of the screen.
UPDATE
The code here is for a custom solution. But if you are looking for a way to just add simple animations to elements as they scroll into view, check out wow.js and animate.css
https://wowjs.uk/
https://animate.style/
// Init variable for timer
let timer;
// Get target element
const el = $("#4");
// Get viewport height
const screen = window.innerHeight;
// Fire callback on window scroll
$(window).scroll(function() {
// Clear timeout just in case
clearTimeout(timer);
// Check if the element already has the class
if (!el.hasClass("active")) {
// Set a delay timer then run the function
timer = setTimeout(check_el_pos, 300);
}
});
function check_el_pos() {
// Clear the timer
clearTimeout(timer);
// Get current scroll position
let scroll_pos = $(window).scrollTop();
// This is the math here. Add scroll position to screen height and you get the bottom of the screen. When that value equals the top offset of the element, we are there.
if (scroll_pos + screen > el.offset().top) {
console.log(scroll_pos + screen);
console.log(el.offset().top);
// Add the classes to the element. Boom, we're done.
el.removeClass("active").addClass("active");
}
}
body,
html {
padding: 0;
margin: 0;
}
.example-grid {
list-style: none;
padding: 0;
margin: 0;
width: 100%;
display: grid;
grid-gap: 40px;
}
.example-grid>li {
list-style: none;
padding: 0;
margin: 0;
width: 100%;
height: 65vh;
background: slategray;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
color: #fff;
font-size: 2em;
line-height: 1em;
transition: background-color 1s;
transition-timing-function: ease-out;
}
.example-grid>li:nth-child(even) {
background: coral;
}
.example-grid>li.active {
background: aquamarine;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul class="example-grid">
<li id="1">1</li>
<li id="2">2</li>
<li id="3">3</li>
<li id="4">4</li>
<li id="5">5</li>
<li id="6">6</li>
<li id="7">7</li>
</ul>
Related
I'm trying to push up two buttons vertically displayed in order to put them above the footer content when I'm scrolling to it.
My two buttons are like this:
And I want them to be like that:
Any ways to make it with Javascript?
Thanks a lot !
Since you provided no code at all, here's my solution based on the assumption your buttons are in a parent div and that div has position: fixed and a right / bottom -property set to a certain amount of pixel.
window.onscroll = function (ev) {
if ((window.innerHeight + window.pageYOffset) >= document.body.offsetHeight - 2) {
document.getElementById("buttons").style.bottom = "100px";
} else {
document.getElementById("buttons").style.bottom = "20px";
}
}
/*
window.onscroll = function (ev) {
let footerHeight = document.getElementsByTagName("footer")[0].offsetHeight;
if ((window.innerHeight + window.pageYOffset) >= (document.body.offsetHeight - footerHeight)) {
document.getElementById("buttons").style.bottom = "100px";
} else {
document.getElementById("buttons").style.bottom = "20px";
}
}
*/
main {
height: 1000px;
}
#buttons {
position: fixed;
bottom: 20px;
right: 20px;
}
button {
background-color: grey;
border-radius: 50%;
display: block;
border: none;
padding: 15px;
margin-top: 10px;
}
<main></main>
<div id="buttons">
<button></button>
<button></button>
</div>
window.onscroll fires of at every scroll-event. It then checks, if the height of the window (window.innerHeight) and the distance scrolled so far (window.pageYOffset) are greater or equal to the total height of the body (document.body.offsetHeight)(- 2) is added because of an annoying mac-'feature'. For more look at this post). If thats the case, it moves the buttons up 100px instead of the 20px normally. If you dont add the else-statement, your buttons will stay at the position even if you scroll up again.
You can now get a bit creative. If you dont want to hit rock bottom of the page to make the buttons move, change the - 2. So you check for the height of your footer, and substract it from the total body height. Your buttons then start to move once the footer is it. Example of that in the javascript snippet, the part that is commented out.
I'm creating a secondary view that will pop-up on clicking on a link. Content within this secondary view is vertically scrollable and horizontally fixed. The issue is: when scroll reach to the top/bottom of the secondary view, the background page will scroll as well, also if attempt to scroll left/right, the background page will also scroll.
I did some search online but mostly suggest to modify the body css. Due to project constraint, we are not allowed to modify any attribute on body. So I'm trying to find a solution without making css change.
I was trying to achieve using this approach:
secondaryView.addEventListener('wheel', (event) => {
if (//mouse scroll up and secondaryView is at top
|| //mouse scroll down and secondaryView is at bottom
|| //mouse scroll left or right) {
event.preventDefault();
}
});
However, the problem is I don't know how to detect whether mouse scroll is horizontal, not able to rely on event.deltaX since it will be non-zero sometimes when scrolling up or down as well.
This problem has a lot of solutions (see this huge Q&A for options), one of which is listening for scroll event instead of the wheel one (after all, you do not want to be limited to mouse wheel, do you?).
The easiest version (given that for some reason you cannot modify styling) is to keep track of the last known scroll position of the primary view (or the window) and as soon as the secondary view is shown (a boolean flag should suffice), start snapping the primary to it with scrollTo.
The rest depends on your exact requirements - if you need the primary to be scrollable while the secondary is open, things become complex (a solution for mouse-based devices may be to track cursor position to determine what is scrolled).
(() => {
const p = document.getElementById("primary");
const s = document.getElementById("secondary");
const b = document.getElementById("show");
let secondaryShown = false;
b.addEventListener("click", () => {
if (secondaryShown) {
s.classList.remove("shown");
secondaryShown = false;
return;
}
s.classList.add("shown");
secondaryShown = true;
});
let lknownYPos = window.scrollY,
lknownXPos = window.scrollX;
window.addEventListener("scroll", ({
target,
currentTarget
}) => {
if (secondaryShown) {
window.scrollTo(lknownXPos, lknownYPos)
}
lknownWindowPos = window.scrollY;
}, true);
})();
body {
margin: 0;
}
.subcontent {
height: 300vh;
width: 300vw;
}
#primary {
width: 200vw;
height: 200vh;
}
#secondary {
overflow-x: scroll;
width: 50vw;
height: 50vh;
background-color: rgba(0, 0, 0, 0.25);
z-index: 9999;
color: white;
display: none;
font-size: 2rem;
text-align: center;
}
.shown {
display: block !important;
}
<div class="content">
<div id="primary">
<button id="show">Switch secondary</button>
<div id="secondary">
<p>Secondary</p>
<div class="subcontent"></div>
</div>
</div>
</div>
I´ve searched the web 100 of times to find something that is like that what I want. I found nothing and tried to do it myself. After two days I give up because of many reasons. So I´m asking you all here if some one can do it for me. Think about a sticks header, you scroll down a website and the header goes with it fixed on the top. So my imagine was, every time the header hits a section with data-color="#2D2D2D", the headers background color will change to it. But wait, I want that it happens linear with a background image, so if he scrolls back its the coloring linear to the color before.
Here is the article I´ve seen. But there its just a image and it is in the content.
https://codyhouse.co/demo/fixed-background-effect/index.html
Here is my Pen (It was just a try)
http://codepen.io/muuvmuuv/pen/MarxYx
Here is a img
I have made a basic example accordiong to your needs, just have a look and tell me if I understood what you said. I have added some extra explanation in the js code and the fiddle is at the end of this post.
A basic HTML markup
<heade id="webHeader">
<nav>
<ul>
<li>Nav item 1</li>
<li>Nav item 2</li>
<li>Nav item 3</li>
</ul>
</nav>
</heade>
<section id="section-1" data-color="#330000"></section>
<section id="section-2" data-color="#00B200"></section>
<section id="section-3" data-color="#803380"></section>
I am going for SCSS but you can easly update to basic CSS (I assumed that the sticky header is by default so I have added a padding to body with the same value as the header height)
$headerHeight: 100px;
body {
padding-top: $headerHeight;
}
#webHeader {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: $headerHeight;
background: #000F1F; /*default background color and fallback if there is no section available for it*/
nav {
padding: 40px;
float: right;
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #fff;
font-weight: 700;
text-decoration: none;
}
}
}
section {
width: 100%;
height: 500px;
background-color: grey;
border-bottom: 1px dashed #fff;
}
And the jQuery code.
(function($){
// cache dom elements
var $header = $('#webHeader');
var $window = $(window);
var headerHeight = $header.outerHeight(true);
var colors = []; // add colors here
var sections = []; // add sections positions
$('section').each(function(){
var $this = $(this);
colors.push($this.data('color'));
sections.push($this.position().top);
});
// duplicate first color
colors.unshift(colors[0]);
$window.on('scroll', function(){
var position = $window.scrollTop() + headerHeight;
var index = inInterval(position, sections);
var distance = position - sections[index];
$header.attr('style', linearGradient( colors[index+1], colors[index], distance ) );
}).trigger('scroll');
// trigger scroll when the page is loaded to update the header color to the current position
})(jQuery);
// Treat array elements as intervals
function inInterval(value, array) {
// cache array length
var arrLen = array.length;
// Add one more value at the end of array to avoid having problems on last item
array.push(array[arrLen-1]*2);
for (var i = 0; i < arrLen+1; i++)
if (value >= array[i] && value <= array[i+1])
return i;
}
function linearGradient(start, end, distance) {
var distanceStart = distance + '%';
var distanceEnd = 100 - distance + '%';
return "background: -webkit-gradient(linear, left top, left bottom, color-stop(0, "+ start +"), color-stop("+ distanceStart +", "+ start +"), color-stop("+ distanceStart +", "+ end +"), color-stop(100, "+ end +")";
}
You can see it working in this fiddle. I would make some updates but I am bit busy for moment, but I recommend you to read more about jQuery debounce and give a try to smart scroll (useful to call bit less scroll events - good for performance)
I hope is what you are looking for :)
I want to add bullet points between my flexbox navigation menu, but only have them appear between flex items on the same line. Not at the beginning or end of line, and not at all if the flex item is on the line by itself.
I've been using this (CSS styling for horizontal list with bullet only between elements) question as a starting point, but modified combined the top two answers to make it work with a flexbox.
Here's what I've got:
http://codepen.io/anon/pen/EjQzWZ
JS:
<script>
$(function() {
var lastElement = false;
$("ul li").each(function(index) {
if (lastElement && lastElement.offset().top == $(this).offset().top) {
$(lastElement).after( $('<li>').attr('class', 'bullet') );
}
lastElement = $(this);
});
});
</script>
CSS (essentially the exact same as the original question I linked to):
<style>
.bullet {
width: 6px;
height: 7px;
background-position: 50% 50%;
background-repeat: no-repeat;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAOElEQVQI113M0Q3AIBRC0aM76P7jmHSmSj/6mibyc4EQkEEWuYtDmU1SXO1d6H7tjgupIl8+P+cD22cff9U1wbsAAAAASUVORK5CYII=);
}
</style>
Most of the time everything renders correctly. But at certain browser widths, bullet points are added where they shouldn't be. For instance, if the window is re-sized to a width of 678 px (according to the codepen.io counter that displays in the middle of screen while re-sizing), a bullet point appears on the right side of the top row when it shouldn't. (Depending on the browser it may work correctly at 678px, but there are several spots that it does occur, and I've only tested this on Chrome and IE11).
Sometimes a refresh is required to fix the bullet points locations, which is understandable, but at some widths refreshing doesn't help and they reappear incorrectly.
I believe the problem is caused by the extra space the bullet points add.
I think it would be a simpler and cleaner solution to do it entirely via CSS instead of JS, but I'm not sure that's possible with flexbox items. Even though the flexbox items appear to take up a wider width than their content, the css selector ::after seems to only calculate the actual content width.
What is going on with my JS? Can I make it better/cleaner?
You can put your bullet logic in a function, then call that function on page load and attach it to the window resize event.
The updated javascript would look like this:
$(function() {
addBullets();
$(window).resize(addBullets);
});
function addBullets(){
$('li.bullet').remove(); //remove bullets before you add new ones
var lastElement = false;
$("ul li").each(function(index) {
if (lastElement && lastElement.offset().top == $(this).offset().top) {
$(lastElement).after( $('<li>').attr('class', 'bullet') );
}
lastElement = $(this);
});
}
See this updated CodePen for a demo.
Also, I don't think this would be possible with pure CSS.
UPDATE
The reason the bullet was showing up on the edge in those certain widths is because the bullets them selves where <li>'s also and were taking up 6px a piece. So when you iterate through the <li>'s It would correctly identify the correct element to put the bullet after, but when the browser renders all the small bullets it would... at certain widths... shove the last <li> to the next line.
To correct this I have created a CSS class for the bullets and will add that to the items that need bullets instead of creating new <li>'s which would take up additional space.
New CSS:
.flex-item {
position:relative; /*added*/
flex: 1 1 auto;
color: #a6a7a9;
font-size:1.5em;
padding: 50px; /*use padding instead of margin for easier absolute positioning of bullet*/
color: black;
text-align: center;
}
.flex-item.bullet:after{
position:absolute;
right:0;
top:50%;
transform:translateY(-50%);
color:blue;
content:"•"; /*could also be "url(data:image/png;base64 ..."*/
}
New Javascript:
function addBullets(){
$('li.bullet').removeClass('bullet'); //remove all bullet classes
var lastElement = false;
$("ul li").each(function(index) {
if (lastElement && lastElement.offset().top == $(this).offset().top)
$(lastElement).addClass('bullet'); //add class
lastElement = $(this);
});
}
See this updated CodePen.
If for whatever reason you can't use this CSS class approach you could add your bullets (as <img> or <div>) inside the <li> and position them absolutely with CSS. As long as they don't contribute to the overall width of the <ul> it should work fine.
You can do this using CSS only.
Delete your .bullet definition, and add this CSS:
.flex-container {
margin-left: -10px; //hide the first bullet on each row
}
.flex-item {
position: relative; //so we can have absolute positioning on the :before
}
.flex-item:before {
content: "•"; //the bullet
position: absolute; //positioned absolutely
left: -55px; //account for the 50px margin and the bullet's width
}
Updated CodePen
div:before{
content:"\b7\a0"; /* \b7 is a middot, \a0 is a space */
font-size:30px;
}
Or try this
div:before{
content:"•";
}
You can use the :not(:first-child) selector to apply the bullet to all but the first child.
.flex-row
{
display: flex;
flex-direction: row;
&.bulleted
{
div:not(:first-child):before{
content: "•";
}
}
}
Basically, I've created some kind of Autolog which has a scrollable DIV using the CSS code below
.myClass {
position: relative;
padding: 39px 14px 14px;
margin-bottom: -1px;
border: 1px solid #ddd;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
height: 200px;
overflow: auto;
}
Basically, data is being placed inside this div dynamically using JQuery as shown below which is fetched from an external source
$.each(data.search, function (i, v) {
$('.myClass').append('my content here');
});
The div is looks as simple as this
<div class="myClass">
</div>
What i want the div to do, is as the content is becoming overflow i want the scrollbar at the side to automatically scroll to the bottom to follow the AutoLog instead of having to manually keep scrolling the bar to the bottom yourself, I'm sure this can be acheived via Javascript but not sure how, if anyone could point me in the right direction that'd be great,
I recently did something similar, where I had a "console" (a div) I was printing to and when it overflowed, you followed it down. here was my "println" function:
printLn: function(str, clss) {
con.print(str, clss);
$output.append('<br/>').animate({ scrollTop: $output.prop('scrollHeight') }, 1);
},
Basically update scrollTop to the new scrollHeight, which more or less says "scroll as far as the scroll height allows" so you "stick" to the bottom.
EDIT
For your situation doing something like:
$.each(data.search, function (i, v) {
$div = $('.myClass');
$div.append('my content here').animate({ scrollTop: $div.prop('scrollHeight') }, 1);
});
This sets the scrollTop property of your div to the max, so it will scroll to the bottom.
Add this to your script after appending content and it should work ..
$(".myClass").animate({
scrollTop: $(".myClass").scrollHeight
}, 300);
// Get the number of pixels scrolled
var intElemScrollTop = element.scrollTop;
intElemScrollTop is an integer corresponding to number of pixels that element's content has been scrolled upward.
// Set the number of pixels scrolled
element.scrollTop = intValue;
check the link https://developer.mozilla.org/en-US/docs/DOM/element.scrollTop