Scroll on div without triggering full page scroll in angular - javascript

I have a website which have one page scroll feature using this - https://alvarotrigo.com/angular-fullpage/
Now in this website, In one page I want to create a division inside which the fullpage scroll feature is disabled and I can scroll that division as normal - like this https://stackblitz.com/edit/angular-kqvraz?file=src%2Fapp%2Fapp.component.html
What I have done till now -
app.component.html
<app-navbar></app-navbar>
<div fullpage id="fullpage2" [options]="config" (ref)="getRef($event)">
<div class="section" id="banner">
//first section
</div>
<div class="section" id="demos">
//second section
</div>
<div class="section" id="prod-solution">
// third section
</div>
<div class="section" id="scroll-solution">
<div style="height: 200px; border: 1px solid; overflow: auto;">
// div where I want to disable full page scroll and enable normal scroll
<div>
Please scroll
<div style="height: 1000px; width: 1000px;"></div>
</div>
</div>
</div>
</div>
app.component.ts
export class AppComponent {
config: any;
fullpage_api: any;
constructor() {
// for more details on config options please visit fullPage.js docs
this.config = {
// fullpage options
licenseKey: 'YOUR LICENSE KEY HERE',
anchors: ['firstPage', 'secondPage', 'thirdPage', 'fourthPage', 'lastPage'],
menu: '#menu',
// fullpage callbacks
afterResize: () => {
console.log("After resize");
},
afterLoad: (origin, destination, direction) => {
console.log(origin.index);
}
};
}
getRef(fullPageRef) {
this.fullpage_api = fullPageRef;
}
}

You should catch the wheel event on the DIV that shouldn't trigger the fullpage scroll and only scroll this element.
Code
Modify the section of your code to match the following one:
<div style="height: 200px; border: 1px solid; overflow: auto;">
<!-- add a scroll event listener -->
<div (wheel)="blockScroll($event)">
Please scroll
<div style="height: 1000px; width: 1000px;"></div>
</div>
</div>
Add the event listener in your app.component.ts:
blockScroll(e) {
let delta = e.deltaY || -e.detail;
e.currentTarget.scrollTop += delta * 30;
e.stopPropagation();
e.preventDefault();
}
Demo
I added a scrolling container in "Section 2" that will only scroll its own content without triggering the fullpage scroll.
Demo on StackBlitz
If you want other scroll events like touch to be handled as well you need to add the relevant event to the <div> as well.

For scrolling on pages higher than 100vh, We want scrolling to be done normally and when we get to the bottom of the page, do a full scroll.
For this purpose, you can use the fullpage.js package, which requires a license. But by typescript, it can be easily implemented.
sample in stackblitz
in file.html use (mousewheel):
<div class="container" id="main-container"
(mousewheel)="changeMouseWheel($event)">
<div class="panel" id="el1"></div>
<div class="panel" id="el2"></div>
<div class="panel" id="el3"></div>
<div class="panel" id="el4"></div>
<div class="panel" id="el5"></div>
<div class="panel" id="el6">
<h1>whit long height</h1>
</div>
</div>
in fil.css:
.panel{
width: 100%;
height: 100vh;
}
#el1 {background-color: antiquewhite}
#el2 {background-color: aliceblue}
#el3 {background-color: beige}
#el4 {background-color: aqua}
#el5 {background-color: #00ffae
}
#el6 {height: 200vh; background-color: #6200ff
}
in file.ts:
changeMouseWheel(e) {
const sectionCount = document.querySelectorAll('.panel').length;
const windowHeight = window.innerHeight;
if (e.deltaY < 0 && this.sectionNumber > 1) {
if (this.hold === false) {
this.hold = true;
this.sectionNumber -= 1;
const element = document.getElementById(`el${this.sectionNumber}`);
this.scroll(element.offsetTop, 0);
setTimeout(() => {
this.hold = false;
}, 500);
e.preventDefault();
}
}
if (e.deltaY > 0 && this.sectionNumber < sectionCount) {
const currentElement = document.getElementById(`el${this.sectionNumber}`);
if (((currentElement.offsetTop + currentElement.offsetHeight) - windowHeight) <= document.documentElement.scrollTop) {
if (this.hold === false) {
this.hold = true;
this.sectionNumber += 1;
console.log(`#el${this.sectionNumber}`);
const nextElement = document.getElementById(`el${this.sectionNumber}`);
this.scroll(nextElement.offsetTop, 0);
setTimeout(() => {
this.hold = false;
}, 500);
e.preventDefault();
}
}
}
}
scroll(topData: number, leftData: number) {
window.scrollTo({
top: topData,
left: leftData,
behavior: 'smooth'
});
}

Related

Change active state on scroll to viewport

I'm trying to make a single static website, which when an div child of comes into viewport (precisely, when div element comes into the upper 50% of the viewport) changes the corresponding div's class in side-nav to "active". It should work scrolling down and up.
So far I've tried several solution from other threads on SO, none successful. I assume I've been approaching this wrong.
$(window).on('scroll', function() {
$("#vars-args").each(function() {
if (elementInViewport2($(this))) {
$(this).find("#div1a").addClass("active");
}
});
});
function elementInViewport2(el) {
var top = el.offsetTop;
var left = el.offsetLeft;
var width = el.offsetWidth;
var height = el.offsetHeight;
while (el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top < (window.pageYOffset + window.innerHeight) &&
left < (window.pageXOffset + window.innerWidth) &&
(top + height) > window.pageYOffset &&
(left + width) > window.pageXOffset
);
}
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script>
<div id="side-nav">
1
2
3
4
5
6
</div>
<div id="content">
<div id="div1">
<!--content-->
</div>
<div id="div2">
<!--content-->
</div>
<div id="div3">
<!--content-->
</div>
<div id="div4">
<!--content-->
</div>
<div id="div5">
<!--content-->
</div>
<div id="div6">
<!--content-->
</div>
</div>
Also note that content of each div inside can be larger than the size of viewport.
I have been having problems getting the javascript to work. Also please note that the current JS is copied from some other thread.
This can be achieved using the IntersectionObserver as told by #cloned in the comments: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
To achieve this, you need a callback function passed as a parameter which is executed once isIntersecting is true, an option object (below it sets the threshold at 50% of the element) and an IntersectionObserver.
The callback toggles the active class to the a element according to the entry's id.
At the end we loop through the divs and make our observer observe them.
const callback = (entries, observer) => {
entries.forEach(entry => {
const navItem = document.querySelector('#' + entry.target.id + 'a');
if (entry.isIntersecting) {
console.log(navItem.getAttribute('id'));
navItem.classList.add('active');
} else {
navItem.classList.remove('active');
}
});
};
const options = {
threshold: 0.5
};
const observer = new IntersectionObserver(callback, options);
const container = document.getElementById('content');
const targetElements = container.querySelectorAll('div');
targetElements.forEach(element => {
observer.observe(element);
});
Here is a JSBin to demonstrate it https://jsbin.com/riyuhediso/47/edit?html,js,console,output
Note that although it demonstrates its feasibility it's not been profiled for performance issues which can be significant so I don't vouch for it.
If you are using Bootstrap you can use the ScrollSpy lib https://getbootstrap.com/docs/4.1/components/scrollspy/ and there is also ScrollMagic which is great http://scrollmagic.io/
You need to filter out which element is inside the viewport with the help of .getBoundingClientRect()
Checkout this
and check if any content has it's top and bottom within the half of the viewport ( window.innerHeight )
I took help of filter function to find out the index of contents that is within the built in function and set the .active class of the corresponding anchor.
Have a look at the snippet:
var direction = 0; // a variable to keep track of scrolled position;
$(window).on('scroll', function() {
// check if window is scrolling up or down;
if ($(window).scrollTop() > direction) { // if true, window scrolling scrolling down;
$('#side-nav').find('a').removeClass('active'); // remove active class from all anchors
$('#side-nav').find('a').eq(
// .eq() selector helps to find elements with index number, and here we pass a filter to find the content that is within the viewport;
$('#content').find('div').filter(function(index) {
return this.getBoundingClientRect().y <= (window.innerHeight / 2) && this.getBoundingClientRect().y + this.getBoundingClientRect().height > window.innerHeight / 2;
}).index()
).addClass('active');
// update the current scroll position now;
direction = $(window).scrollTop();
} else { // if false, window scrolling scrolling up;
$('#side-nav').find('a').removeClass('active'); // remove active class from all anchors
$('#side-nav').find('a').eq(
$('#content').find('div').filter(function(index) {
return this.getBoundingClientRect().y < (window.innerHeight / 2) && this.getBoundingClientRect().y + this.getBoundingClientRect().height > window.innerHeight / 2;
}).index()
).addClass('active');
// update the current scroll position now;
direction = $(window).scrollTop();
}
});
* {
margin: 0;
padding: 0;
}
#side-nav {
/* feel free to remove or change, only for testing */
position: fixed;
width: 100%;
top: 0;
padding: 15px;
}
#side-nav a {
/* feel free to remove, only for testing */
text-decoration: none;
color: grey;
margin-right: 5px;
font-size: 24px;
font-weight: bold;
}
#side-nav a.active {
color: #000;
/* sets color for the default active class */
}
#content div {
min-height: 600px;
background-color: #cecece;
border-bottom: 1px solid #fff;
box-sizing: border-box;
padding: 50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="side-nav">
<a href="" id="div1a" class='active'>1</a>
<!-- set a default class assuming the first one will be in viewport while window loads -->
2
3
4
5
6
</div>
<div id="content">
<div id="div1">
<p>One</p>
</div>
<div id="div2">
<p>Two</p>
</div>
<div id="div3">
<p>Three</p>
</div>
<div id="div4">
<p>Four</p>
</div>
<div id="div5">
<p>Five</p>
</div>
<div id="div6">
<p>Six</p>
</div>
</div>

jQuery - element crossing another element

I have a fixed div on the page which contains a logo and as the user scrolls and this logo passes over other divs I wnat to the change the colour of the logo.
I have this working over a single div but need to it work across multiple so any help appreciated.
The WIP site can be seen here... dd.mintfresh.co.uk - if you scroll down you'll (hopefully) see the logo change from black to white as it crosses an illustrated egg. I need the same to happen when it crosses other divs further down the page.
The script so far...
jQuery(window).scroll(function(){
var fixed = jQuery("logo");
var fixed_position = jQuery("#logo").offset().top;
var fixed_height = jQuery("#logo").height();
var toCross_position = jQuery("#egg").offset().top;
var toCross_height = jQuery("#egg").height();
if (fixed_position + fixed_height < toCross_position) {
jQuery("#logo img").css({filter : "invert(100%)"});
} else if (fixed_position > toCross_position + toCross_height) {
jQuery("#logo img").css({filter : "invert(100%)"});
} else {
jQuery("#logo img").css({filter : "invert(0%)"});
}
}
);
Any help appreciated. Thanks!
you need to fire a div scroll event. you can assign
$("div1").scroll(function(){
//change the color of the div1
}
});
$("div2").scroll(function(){
//change the color of the div2
}
});
or you can assign a class to divs which you want to change the color
$(".div").scroll(function(){
//change the color of the div which you are scrolling now
}
});
You can use like this :-
$(window).scroll(function() {
var that = $(this);
$('.section').each(function() {
var s = $(this);
if (that.scrollTop() >= s.position().top) {
if(s.hasClass('active')) {
$('.logo').addClass('invert');
} else {
$('.logo').removeClass('invert');
}
}
});
});
body {
padding: 0;
margin: 0;
}
div {
background: #f00;
height: 400px;
}
.logo {
position: fixed;
top: 0;
left: 0;
width: 100px;
}
.logo.invert {
filter: invert(100%);
}
div:nth-child(even) {
background: #ff0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<img src="https://dd.mintfresh.co.uk/wp-content/uploads/2018/06/DD_logo.svg" class="logo" />
<div id="page1" class="section"></div>
<div id="page2" class="section active"></div>
<div id="page3" class="section"></div>
<div id="page4" class="section active"></div>
<div id="page5" class="section"></div>
As your site code you can do like this :
$(window).scroll(function() {
var that = $(this);
$('#content > section').each(function() {
var s = $(this);
if (that.scrollTop() >= s.position().top) {
if(s.hasClass('black')) {
$('#logo img').css({filter: 'invert(0%)'});
} else {
$('#logo img').css({filter: 'invert(100%)'});
}
}
});
});

Fire AJAX jQuery if scroll reach to each div

I want make ajax fire if scroll reach to each div element.
Item div 1
Item div 2 (fire ajax if scroll to this element)
Item div 3 (fire ajax again if scroll to this element)
Item .....N
I use this code, but only fire if scroll to end.
$( window ).scroll( function() {
if( $( window ).scrollTop() == $( document ).height() - $( window ).height() ) {
alert('Fire!');
}
});
Please help.
Use $.offset().top instead of heights.
To check all sections you can use $.each(). Since I am guessing you only want to fire the event once, you will need a variable to remember all sections, that already fired.
let firedEvents = [];
$(window).scroll(function() {
$("div.section").each(function() {
if (!firedEvents.includes(this) && $(window).scrollTop() > $(this).offset().top) {
firedEvents.push(this);
alert("fire " + $(this).data("nr"));
}
});
});
div {
height: 500px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="section" data-nr="0" style="background-color: red;"></div>
<div class="section" data-nr="1" style="background-color: green;"></div>
<div class="section" data-nr="2" style="background-color: blue;"></div>
<div class="section" data-nr="3" style="background-color: yellow;"></div>
I took the liberty of writing a simple html that will fullfil your needs.
In my example scroll happens via button click; u can change it with your usecase; Hope this helps; here's the fiddle https://jsfiddle.net/39z82axt/
html
<div id="wrapper">
<div class="divel">Element 1</div>
<div class="divel">Element 2</div>
<div class="divel">Element 3</div>
<div class="divel">Element 4</div>
<div class="divel">Element 5</div>
</div>
<button id="scrollButton">
Click to Scroll
</button>
css
.divel {
height:80px;
}
#wrapper {
height: 100px;
overflow:scroll;
}
js
let crossedFirstDiv = false,
crossedSecondDiv = false;
document.getElementById('scrollButton').onclick = function() {
document.getElementById("wrapper").scrollTop += 10;
let scrollLocation = scrollPosition("wrapper");
if (scrollLocation > 10 && scrollLocation <20 && crossedFirstDiv == false) {
alert("crossing div 1");
crossedFirstDiv = true;
}
else if (scrollLocation > 20 && scrollLocation <30 && crossedSecondDiv == false) {
crossedSecondDiv = true;
alert("crossing div 2");
}
// and so on..
}
function scrollPosition(elementId) {
let a = document.getElementById(elementId).scrollTop;
let b = document.getElementById(elementId).scrollHeight - document.getElementById(elementId).clientHeight;
let c = a / b;
return Math.floor(c * 100);
}
$(window).scroll( function() {
var scrolled = $( window ).scrollTop();
$('div:not(.fired)').each(function () {
var position_of_div = $(this).offset();
if (scrolled > position_of_div.top) {
$(this).addClass('fired');
alert('fire');
}
});
});
Save the scrolled pixels in an variable. On scroll get the position of each div. If the amount of pixels scrolled is greater than the offset of the div (related to the document) fire a AJAX call.

How to set js function scroll let it don't exceed parent‘s bottom?

The html, js, css example is https://jsfiddle.net/t9mfmaa3/5/.
/* Latest compiled and minified JavaScript included as External Resource */
$(function() {
var $sidebar = $("#e"),
$window = $(window),
$offset = $sidebar.offset(),
$topPadding = 15;
$window.scroll(function() {
if ($window.scrollTop() > $offset.top) {
$sidebar.stop().animate({
marginTop: $window.scrollTop() - $offset.top + $topPadding
});
} else {
$sidebar.stop().animate({
marginTop: 0
});
}
});
});
/* Latest compiled and minified CSS included as External Resource*/
/* Optional theme */
#import url('//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-theme.min.css');
body {
margin: 10px;
}
#c {
background-color: red;
height: 2400px
}
#e {
background-color: lightblue;
height: 600px
}
#b {
height: 2400px;
background-color: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<div class="container">
<div class="row">
<div class="a">
<div id="b" class="column col-xs-3 col-sm-3">
<div id="e" class="">
blue
</div>
</div>
<div id="c" class="center_column col-xs-9 col-sm-9">
red
</div>
</div>
</div>
</div>
I tried to make blue block not exceed yellow block which means the blue one always in yellow block. My idea is to set code to detect block yellow and block blue. But I didn't success. Anybody has any suggestion? Thanks
If you are already using bootstrap, you may as well use their affix javascript.
getbootstrap.com
Here is an example:
jsfiddle.net
$(function() {
var $sidebar = $("#e"),
$body = $('body'),
$parent = $('#b'),
topPadding = 15,
offset=$sidebar.offset();
$sidebar.affix({
offset: {
top: function() {
return $parent.offset().top - topPadding;
},
bottom: function() {
return $(document.body).height() - ($parent.offset().top + $parent.outerHeight());
}
}
});
});
You might notice it act a little weird and jumpy near the end, but that should go away when using it on a real site (instead of inside an iframe)

Do not run jQuery function when <a> tag clicked

I currently work with some jQuery, where i have got some problems.
I got this code
if ($(".accordion").length > 0) {
$(".accordion").each(function() {
var item = $(this).find(".accordion-text");
var height = item.outerHeight() + 20;
item.data("height", height + "px").css("height", "0px");
})
}
$(".accordion").on("click", function(e) {
foldOut($(this));
});
function foldOut(accordien) {
console.log(accordien);
var item = $(accordien).find(".accordion-text");
if ($(accordien).hasClass("accordion-open")) {
$(item).stop().transition({
height: '0px'
}, 500, 'in-out');
$(accordien).find(".accordionArrow").removeClass("accordionBgActive");
console.log($(accordien).find(".accordionArrow"));
} else {
$(accordien).find(".accordionArrow").addClass("accordionBgActive");
$(item).stop().transition({
height: item.data("height")
}, 500, 'in-out');
}
$(accordien).toggleClass("accordion-open");
}
But inside the div that is folding out, there may be an a tag, and when i click on the a tag it opens the link but also folds the div..
How can i get the div not to fold when the click is on an a tag?
HTML Where its "closed"
<div class="row">
<div class="overflow-hide rel">
<div class="accordion rel col-md-12 no-pad">
<div class="accordionHeaderDiv">
<h3>Test</h3>
<div class="accordion-header-teaser">
<p>TestTestTestTestTestTestTestTestTestTest</p>
</div>
</div>
<div class="accordion-text" style="height: 0px;">
<p>TestTestTestTestTestTestTestTestTestTestTestTestTestTestTestTestTestTestTestTestTestTestTestTestTest</p>
<p>Test</p>
</div>
<div class="accordionArrow" style=" position: absolute; top: 0; cursor: pointer; right: 43px; height: 30px;"></div>
</div>
<div class="clearfix"></div>
</div>
</div>
Filter it out regarding event target:
$(".accordion").on("click", function(e) {
if(e.target.tagName.toLowerCase() === "a") return;
foldOut($(this));
});
As anchor can contains other contents, a more relevant way would be:
$(".accordion").on("click", function (e) {
if ($(e.target).closest('a').length) return;
foldOut($(this));
});

Categories

Resources