CSS half page fixed half scroll not working smoothly - javascript

Using a code snippet I found online https://codepen.io/mattyfours/pen/LNgOWx
I made slight modifications and now, although the scroll/fixed functionality works, my 'fixed' side jumps when scrolling. I added 'background-size: contain' onto the fixed side which only works when scrolling has commenced However, on page load/ when no scrolling has occurred the image remains at its full-size meaning once scrolling begins the image goes from full width to 'contained' and created a jump.
Github:
https://github.com/tavimba/fixed-scroll
The issue can be seen in about.html
javascript:
var window_height;
var header_height;
var doc_height;
var posTop_sticky1;
var posBottom_sticky1;
var posTop_s2;
var posBottom_s2;
$(document).ready(function() {
getValues();
});
$(window).scroll(function(event) {
var scroll = $(window).scrollTop();
if (scroll < posTop_sticky1) {
$('.sticky').removeClass('fixy');
$('.sticky').removeClass('bottom');
}
if (scroll > posTop_sticky1) {
$('.sticky').removeClass('fixy');
$('.sticky').removeClass('bottom');
$('#sticky1 .sticky').addClass('fixy');
}
if (scroll > posBottom_sticky1) {
$('.sticky').removeClass('fixy');
$('.sticky').removeClass('bottom');
$('#sticky1 .sticky').addClass('bottom');
$('.bottom').css({
'max-height': window_height + 'px'
});
}
if (scroll > posTop_s2 && scroll < posBottom_s2) {
$('.sticky').removeClass('fixy');
$('.sticky').removeClass('bottom');
$('#s2 .sticky').addClass('fixy');
}
});
function getValues() {
window_height = $(window).height();
doc_height = $(document).height();
header_height = $('header').height();
//get heights first
var height_sticky1 = $('#sticky1').height();
var height_s2 = $('#s2').height();
//get top position second
posTop_sticky1 = header_height;
posTop_s2 = posTop_sticky1 + height_sticky1;
//get bottom position 3rd
posBottom_sticky1 = posTop_s2 - header_height;
posBottom_s2 = doc_height;
}
var rtime;
var timeout = false;
var delta = 200;
$(window).resize(function() {
rtime = new Date();
if (timeout === false) {
timeout = true;
setTimeout(resizeend, delta);
}
});
function resizeend() {
if (new Date() - rtime < delta) {
setTimeout(resizeend, delta);
} else {
timeout = false;
getValues();
}
}
CSS:
section {
width: 100%;
min-height: 100%;
float: left;
position: relative;
}
header {
width: 100%;
height: 5vw;
background-color: black;
float: left;
}
.sticky {
height: 100%;
width: 60%;
float: left;
position: absolute;
}
.sticky.fixy {
position: fixed;
top: 0;
left: 0;
}
.sticky.bottom {
position: absolute;
bottom: 0;
}
.green {
background-image: url(../imgs/front%20view.jpg);
background-size: cover;
}
.stickyBg {
background-image: url(../imgs/bonnets.jpg);
background-size: cover;
}
.scrolling {
float: right;
width: 50%;
padding: 20px;
h5 {
margin-left: 135px;
}
p {
margin-left: 135px;
font-size: 1em;
line-height: 1.5;
}
}

The jump is caused by change of position from absolute to fixed in combination with 100% height.
Besides, the above code has the following flaws:
Max-height assignment looks inconsistent.
JS assumes exactly two sections in HTML: #section1 and #s2. The third section won't work.
Window resize is handled incorrectly. The half-page-scroll logic consists of the two steps: CalculateVars and AdjustDOMElementPositions. For the smooth look these two actions have to be done in 3 cases: onDocumentLoad, onResize and onScroll.
Global vars.
Looks like, it needs some refactoring to get work ;)
<section class="js-half-page-scroll-section"><!-- Get rid of id -->
...
</section>
function halfPageScroll() {
let scrollTop, windowHeight, headerHeight; // and some other common vars
// Calculate vars
scrollTop = $(window).scrollTop();
//...
let repositionSection = function($section) {
let sectionHeight; // and some other vars related to current section
// Some logic
}
$('.js-half-page-scroll-section').each((i, el) => repositionSection($(el)));
}
$(document).ready(halfPageScroll);
$(window).scroll(halfPageScroll);
$(window).resize(halfPageScroll); // TODO: add some debounce wrapper with timeouts

Related

autoscroll (like movie credits) but the user can takeover by simply scrolling

I am looking into creating a website which will serve as a a digital leaflet for a musical theatre. The idea is to have an autoscrolling credits list as landingpage. I've looked at examples on codepen to see how this effect is been achieved. But I would also like the user to interact and scroll themselves if they want to. When they stop scrolling the credits will turn back to autoscroll. I didn't find any example who tackles this issue. Does someone of you know a script (JS, or plain css…) that can help me with this?
The most straightforward way is to set up a requestAnimationFrame() function and increment the value accordingly, then set the scroll position to it.
Then add the wheel event to detect when a user scrolls (don't use the 'scroll' event though, it already gets called when you change the scrollTop value of the body), also don't forget to cancel the requestAnimationFrame() function. The code would look something like this:
let body = document.body,
starter = document.querySelector("h1"),
scroll_counter = 0,
scrolled,
auto_scroll_kicked = false;
starter.addEventListener("click", start_scrolling);
function start_scrolling() {
auto_scroll_kicked = true;
body.offsetHeight > scroll_counter
? (scroll_counter += 1.12)
: (scroll_counter = body.offsetHeight);
document.documentElement.scrollTop = scroll_counter;
scroller = window.requestAnimationFrame(start_scrolling);
if (scroll_counter >= body.offsetHeight) {
window.cancelAnimationFrame(scroller);
}
}
window.addEventListener("wheel", (e) => {
if (auto_scroll_kicked) {
window.cancelAnimationFrame(scroller);
scroll_counter = 0;
}
});
Play with the codepen if you'd like:
https://codepen.io/SaltyMedStudent/pen/QWqVwaR?editors=0010
There are many options to use: easing functions and etc, but hope this will suffice for now.
In your auto scroll routine before changing position check if previous position is the same as current scrolling position, if it's not - the user scrolled it:
let el = document.documentElement,
footer = document.getElementById("status").querySelectorAll("td"),
scroll_position = 0,
scroll_speed = 0,
scroll_delta = 1.12,
scroller,
status = "stopped";
el.addEventListener("click", scroll);
info();
function scroll(e)
{
if (e.type == "click")
{
window.cancelAnimationFrame(scroller);
scroll_position = el.scrollTop; //make sure we start from current position
scroll_speed++; //increase speed with each click
info("auto scroll");
}
//if previous position is different, this means user scrolled
if (scroll_position != el.scrollTop)
{
scroll_speed = 0;
info("stopped by user");
return;
}
el.scrollTop += scroll_delta * scroll_speed; //scroll to new position
scroll_position = el.scrollTop; //get the current position
//loop only if we didn't reach the bottom
if (el.scrollHeight - el.scrollTop - el.clientHeight > 0)
{
scroller = window.requestAnimationFrame(scroll); //loop
}
else
{
el.scrollTop = el.scrollHeight; //make sure it's all the way to the bottom
scroll_speed = 0;
info("auto stopped");
}
}
function info(s)
{
if (typeof s === "string")
status = s;
footer[1].textContent = el.scrollTop;
footer[3].textContent = scroll_speed;
footer[5].textContent = status;
}
//generate html demo sections
for(let i = 2, section = document.createElement("section"); i < 6; i++)
{
section = section.cloneNode(false);
section.textContent = "Section " + i;
document.body.appendChild(section);
}
//register scroll listener for displaying info
window.addEventListener("scroll", info);
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body
{
font-family: "Roboto", Arial;
user-select: none;
}
section
{
min-height: 100vh;
font-size: 3em;
font-weight: 500;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
}
section:nth-child(even)
{
background: #0b0d19;
}
section:nth-child(odd)
{
background: #131524;
}
#status
{
position: fixed;
bottom: 0;
color: #fff;
margin: 0.5em;
}
#status td:first-of-type
{
text-align: end;
padding-right: 0.4em;
}
#status td:last-of-type
{
font-weight: bold;
}
<section>
Click to start Scrolling
</section>
<table id="status">
<tr><td>position:</td><td></td></tr>
<tr><td>speed:</td><td></td></tr>
<tr><td>status:</td><td></td></tr>
</table>

Infinite scroll doesn't work when scrolled all the way to the end of the div

I am trying to make an infinite scroll in my chat. I'm using the scroll event to check if scrolltop < clientHeight and call a function loadMore if it is. This works pretty well as long as you never scroll to the very top. I made a gif to show this (hopefully it makes sense):
If you still have more room to scroll when the older messages get loaded, you keep your place and the scroll bar gets pushed down.
But if you are scrolled all the way to the top when the older messages get loaded, the scroll bar stays pinned to the top and you lose your place (also the scroll event stops being fired, so you stop loading messages unless you scroll down a little)
Has anyone else experienced this? And what did you do to fix it? Any advice appreciated. Thanks!
updated the answer to support 2 directions (up or down) and loading paddings. Please run the snippet in expanded mode, inline preview frame is too small for the scrollable list.
var isLoadingAlready = false;
var upDirection = true; // to load records on top of the list; false to load them to the end of the list
var loadThreshold = 100; // distance to the edge (in pixels) to start loading
var howManyDataLoadsAvailable = 5;
if (upDirection){
$('.scroll')[0].scrollTop = 100000; // scrolling all the way down
$('.scroll').css('paddingTop', loadThreshold);
} else {
$('.scroll').css('paddingBottom', loadThreshold);
}
$('.scroll').on('scroll', function () {
var s = this; // picking DOM element
if (s) { // just to be sure/safe
var scrollableHeight = s.scrollHeight - s.clientHeight;
if (scrollableHeight > 0) {
var scrollTop = s.scrollTop;
var distToTheEdge = upDirection?scrollTop:scrollableHeight - scrollTop;
if (distToTheEdge < loadThreshold && !isLoadingAlready) {
isLoadingAlready = true;
loadMoreRecords(function () { // assuming you have a callback to allow next loading
isLoadingAlready = false;
});
}
}
}
});
loadMoreRecords();
function loadMoreRecords(doneCallback){
$('.scroll').addClass('loading');
// simulating the actual loading process with setTimeout
setTimeout(function(){
// simulated items to insert:
var items = [];
if (howManyDataLoadsAvailable-- > 0){
for (var i = 0; i < 10; i++){
items.push($('<li>').text('msg: '+(i+1)+', parts left: '+howManyDataLoadsAvailable));
}
}
var $se = $('.scroll'); // scrollable DOM element
var $ul = $('.scroll ul');
var se = $se[0];
if (upDirection) {
var hBefore = $ul.height();
$ul.prepend(items);
var hDiff = $ul.height() - hBefore;
se.scrollTop = Math.max(hDiff, loadThreshold);
} else {
$ul.append(items);
se.scrollTop = se.scrollHeight - se.clientHeight - Math.max(se.scrollHeight - se.clientHeight - se.scrollTop, loadThreshold);
}
$se.removeClass('loading');
if (typeof(doneCallback) === 'function'){
doneCallback();
}
}, 500);
}
.scroll{
overflow-y: auto;
max-height: 200px;
border: 2px dashed #aaa;
padding: 0.5em;
margin: 1em;
}
.scroll.loading{
background-color: #f5f5f5;
}
ul{
list-style: none;
padding: 0;
}
li{
padding: 0.5em;
border: 1px solid #eee;
border-radius: 0.5em;
margin: 0.2em;
animation: colorchange 1200ms;
background: white;
box-shadow: 1px 1px 5px 0px rgba(0,0,0,0.05);
}
#keyframes colorchange
{
0% {background: #def;}
100% {background: white;}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div class="scroll">
<ul></ul>
</div>

How to change navigation text color on fullscreen scroll based on background?

Title says it all really. Here is an example of what I'm trying to achieve navigation color change image based on background
The problem is that it needs to work with a site that's using a scroll-jacking parallax type effect, here is the site I'm trying to achieve this effect with demo website
Modify the scroll script a bit
Check demo here
Created the function toggleHeaderColor to check the current section. Since the scroll script is indexing each section in order 0 (i.e. section_1) ,1 (i.e. section_2),2 (i.e. section_2),3 (i.e. section_3),4 (i.e. section_2) and so on. Every time you scroll it gets updated.
In scroll script there are two function nextItem() and previousItem()form which we get the current slide index and on that we can call our function to toggle dark class on header elements.
JS:
var sectionBlock = $(".section-item");
var getCurrentSlideAttr = 0;
function toggleHeaderColor(getCurrentSlideAttr) {
if (getCurrentSlideAttr == 0) {
$(".menu-link, .menu-link-logo, .menu-link-last").removeClass("dark");
}
if (getCurrentSlideAttr == 1) {
$(".menu-link, .menu-link-logo, .menu-link-last").addClass("dark");
}
if (getCurrentSlideAttr == 2) {
$(".menu-link, .menu-link-logo, .menu-link-last").removeClass("dark");
}
if (getCurrentSlideAttr == 3) {
$(".menu-link, .menu-link-logo, .menu-link-last").removeClass("dark");
}
if (getCurrentSlideAttr == 4) {
$(".menu-link, .menu-link-logo, .menu-link-last").addClass("dark");
}
}
var ticking = false;
var isFirefox = /Firefox/i.test(navigator.userAgent);
var isIe =
/MSIE/i.test(navigator.userAgent) ||
/Trident.*rv\:11\./i.test(navigator.userAgent);
var scrollSensitivitySetting = 30;
var slideDurationSetting = 800;
var currentSlideNumber = 0;
var totalSlideNumber = $(".section-item").length;
function parallaxScroll(evt) {
if (isFirefox) {
delta = evt.detail * -120;
} else if (isIe) {
delta = -evt.deltaY;
} else {
delta = evt.wheelDelta;
}
if (ticking != true) {
if (delta <= -scrollSensitivitySetting) {
ticking = true;
if (currentSlideNumber !== totalSlideNumber - 1) {
currentSlideNumber++;
nextItem();
}
slideDurationTimeout(slideDurationSetting);
}
if (delta >= scrollSensitivitySetting) {
ticking = true;
if (currentSlideNumber !== 0) {
currentSlideNumber--;
}
previousItem();
slideDurationTimeout(slideDurationSetting);
}
}
}
function slideDurationTimeout(slideDuration) {
setTimeout(function() {
ticking = false;
}, slideDuration);
}
var mousewheelEvent = isFirefox ? "DOMMouseScroll" : "wheel";
window.addEventListener(mousewheelEvent, _.throttle(parallaxScroll, 60), false);
function nextItem() {
getCurrentSlideAttr = currentSlideNumber;
toggleHeaderColor(getCurrentSlideAttr);
var $previousSlide = $(".section-item").eq(currentSlideNumber - 1);
$previousSlide
.css("transform", "translate3d(0,-130vh,0)")
.find(".content-wrapper")
.css("transform", "translateY(40vh)");
currentSlideTransition();
}
function previousItem() {
//console.log($(".section-item").eq(currentSlideNumber).attr('id'))
getCurrentSlideAttr = currentSlideNumber;
toggleHeaderColor(getCurrentSlideAttr);
var $previousSlide = $(".section-item").eq(currentSlideNumber + 1);
$previousSlide
.css("transform", "translate3d(0,30vh,0)")
.find(".content-wrapper")
.css("transform", "translateY(30vh)");
currentSlideTransition();
}
function currentSlideTransition() {
var $currentSlide = $(".section-item").eq(currentSlideNumber);
$currentSlide
.css("transform", "translate3d(0,-15vh,0)")
.find(".content-wrapper")
.css("transform", "translateY(15vh)");
}
Update
You can actually choose a specific text color over white/black backgrounds using css blend modes.
Example with specific colors (green over white and red over black in this case):
html, body {
margin: 0;
}
h1 {
position: fixed; top: 0; left: 0;
width: 100vw;
text-align: center;
mix-blend-mode: difference;
color: white;
z-index: 1;
}
div {
position: relative;
width: 100vw;
height: 100vh;
background: white;
margin: 0;
}
div:nth-of-type(2n) {
background: black;
}
div:after {
content: '';
position: absolute; top: 0; left: 0;
width: 100%;
height: 100%;
z-index: 2;
}
div:nth-of-type(2n):after {
background: red;
mix-blend-mode: multiply;
}
div:nth-of-type(2n + 1):after {
background: green;
mix-blend-mode: screen;
}
<h1>Scroll to see effect</h1>
<div></div>
<div></div>
<div></div>
I think the only way you would be able to choose the exact partial colors using SVG text or paths.
A simple example with mix-blend-mode:
html, body {
margin: 0;
}
h1 {
position: fixed; top: 0; left: 0;
width: 100vw;
text-align: center;
mix-blend-mode: difference;
color: white;
z-index: 1;
}
div {
width: 100vw;
height: 100vh;
background: black;
}
div:nth-of-type(2n) {
background: white;
}
<h1>Scroll to see effect</h1>
<div></div>
<div></div>
<div></div>
Browser support
https://css-tricks.com/reverse-text-color-mix-blend-mode/
Try Adding mix-blend-mode property.
Add this property to your .navigation-menu class
CSS
.navigation-menu{
mix-blend-mode: difference;
}
Hope this Helps...

Check when element appears in the viewport -> addClass

I have a lot of objects in the dom tree, on which i'm adding new class, when they appeat in the viewport. But my code is very slow - it causes page to slow down...
I have such dom:
...
<span class="animation"></span>
...
and such jquery:
$.each($('.animation'), function() {
$(this).data('offset-top', Math.round($(this).offset().top));
});
var wH = $(window).height();
$(window).on('scroll resize load touchmove', function () {
var windowScroll = $(this).scrollTop();
$.each($('.animation'), function() {
if (windowScroll > (($(this).data('offset-top') + 200) - wH)){
$(this).addClass('isShownClass');
}
});
});
maybe i can somehow speed up my scroll checking and class applying?
You can use the Intersection Observer API to detect when an element appears in the viewport. Here is an example that adds a class to an element that is scrolled into the viewport and animates the background color from red to blue:
var targetElement = document.querySelector('.block');
var observer = new IntersectionObserver(onChange);
observer.observe(targetElement);
function onChange(entries) {
entries.forEach(function (entry) {
entry.target.classList.add('in-viewport');
observer.unobserve(entry.target);
});
}
body {
margin: 0;
height: 9000px;
}
.block {
width: 100%;
height: 200px;
margin-top: 2000px;
background-color: red;
transition: background 1s linear;
}
.block.in-viewport {
background-color: blue;
}
<div class="block">
</div>
The Intersection Observer API method works on chrome only, but the performance faster by 100%. The code below loads in 3/1000 second
$(document).ready(function () {
'use strict';
var startTime, endTime, sum;
startTime = Date.now();
var anim = $('.animation');
anim.each(function (index, elem) {
var animoffset = $(elem).offset().top;
$(window).on('scroll resize touchmove', function() {
var winScTop = $(this).scrollTop();
var windowHeight = $(window).height();
var winBottom = winScTop + windowHeight;
if ( winBottom >= animoffset ) {
$(elem).addClass('showed');
}
});
});
endTime = Date.now();
sum = endTime - startTime;
console.log('loaded in: '+sum);
});
html {
height: 100%;
}
body {
margin: 0;
height: 9000px;
}
.animation {
display: block;
width: 400px;
height: 400px;
background-color: blue;
margin-top: 1000px;
}
.animation:not(:first-of-type) {
margin-top: 10px;
}
.animation.showed {
background-color: yellow;
transition: all 3s ease
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<span class="animation"></span>
<span class="animation"></span>
<span class="animation"></span>
<span class="animation"></span>
IntersectionObserver has a limited support in browsers, but it's improving.
I'm basically lazy loading the polyfill only if the browser user is loading my website in doesn't support IntersectionObserver API with the code bellow.
loadPolyfills()
.then(() => /* Render React application now that your Polyfills are
ready */)
/**
* Do feature detection, to figure out which polyfills needs to be imported.
**/
function loadPolyfills() {
const polyfills = []
if (!supportsIntersectionObserver()) {
polyfills.push(import('intersection-observer'))
}
return Promise.all(polyfills)
}
function supportsIntersectionObserver() {
return (
'IntersectionObserver' in global &&
'IntersectionObserverEntry' in global &&
'intersectionRatio' in IntersectionObserverEntry.prototype
)
}

Match parent to absolute children? (responsive Carousel)

Have worked out a solution, see the bottom!
I'm experimenting with a responsive carousel (fluid). I have elements stacked on top of each other so that the width can be fluid depending on the width of the parent. The issue is I need the parent to have overflow hidden which is not possible with children that are absolute positioned.
Tip on cleaning up the JS are appreciated too!
Does anyone have any ideas how to improve this or alternatives? Heres the fiddle: http://jsfiddle.net/j35fy/5/
.carousel-wrap {
position: relative;
}
.carousel-item {
position: absolute;
top: 0;
}
$.fn.mwCarousel = function(options) {
//Default settings.
var settings = $.extend({
changeWait: 3000,
changeSpeed: 800,
reveal: false,
slide: true,
autoRotate: true
}, options );
var CHANGE_WAIT = settings.changeWait;
var CHANGE_SPEED = settings.changeSpeed;
var REVEAL = settings.reveal;
var SLIDE = settings.slide;
var AUTO_ROTATE = settings.autoRotate;
var $carouselWrap = $(this);
var SLIDE_COUNT = $carouselWrap.find('.carousel-item').length;
var rotateTimeout;
if (AUTO_ROTATE) {
rotateTimeout = setTimeout(function(){
rotateCarousel(SLIDE_COUNT-1);
}, CHANGE_WAIT);
}
function rotateCarousel(slide) {
if (slide === 0) {
slide = SLIDE_COUNT-1;
rotateTimeout = setTimeout(function(){
$('.carousel-item').css('margin', 0);
$('.carousel-item').show();
}, CHANGE_WAIT);
if (REVEAL) {
$($carouselWrap.find('.carousel-item')[slide]).slideToggle(CHANGE_SPEED);
} else if (SLIDE) {
var carouselItem = $($carouselWrap.find('.carousel-item')[slide]);
carouselItem.show();
var itemWidth = carouselItem.width();
carouselItem.animate({margin: 0}, CHANGE_SPEED);
} else {
$($carouselWrap.find('.carousel-item')[slide]).fadeIn(CHANGE_SPEED);
}
slide = slide+1;
} else {
if (REVEAL) {
$($carouselWrap.find('.carousel-item')[slide]).slideToggle(CHANGE_SPEED);
} else if (SLIDE) {
var carouselItem = $($carouselWrap.find('.carousel-item')[slide]);
var itemWidth = carouselItem.width();
carouselItem.animate({marginLeft: -itemWidth, marginRight: itemWidth}, CHANGE_SPEED);
} else {
$($carouselWrap.find('.carousel-item')[slide]).fadeOut(CHANGE_SPEED);
}
}
rotateTimeout = setTimeout(function(){
rotateCarousel(slide-1);
}, CHANGE_WAIT);
}
}
$('.carousel-wrap').mwCarousel();
Solution
The first slide actually never moves (last one visible) so that one is set to position: static and all works nicely.
I think by just changing your CSS you're actually there:
.carousel-wrap {
position: relative;
overflow:hidden;
height:80%;
width:90%;
}
Demo: http://jsfiddle.net/robschmuecker/j35fy/2/
Discovered the solution is in fact simple, as the first slide in the DOM (the last you see) never actually moves itself I can set that one slide to be position: static and thus the carousel wrap will set it's height accordingly.
http://jsfiddle.net/j35fy/7/
.container {
background: aliceblue;
padding: 3em;
}
.carousel-wrap {
position: relative;
overflow:hidden;
}
.carousel-item:first-child {
position:static;
}
.carousel-item {
position: absolute;
top: 0;
width: 100%;
}
img {
width: 100%;
}

Categories

Resources