changing scroll top inside angular directive doesn't work - javascript

In my directive I wrote my logic for dynamic pagination (lazy loading), each time the user scroll to the bottom of the page I append more elements to it , this works fine but I want to to change the scroll position after that but it doesn't work.
This is my code :
link: function(scope, element) {
var usersArea = $(".usersArea");
usersArea.bind("scroll", function() {
var scrollHeight = $(this)[0].scrollHeight;
var scrollTop = $(this)[0].scrollTop;
var clientHeight = $(this)[0].clientHeight;
var downloadMore = scrollHeight - scrollTop - clientHeight < 50;
if (downloadMore) {
var childScope = scope.$new();
usersContainer = scope.displayPortion(usersContainer);
if (usersContainer) {
$compile(usersContainer)(childScope);
//This doesn't work !!
$(this)[0].scrollTop = 500;
}
}
});
}
I tried to change the scroll position using native javascript and with JQuery but nothings seem to work, any suggestions ?

Since the compile is not immediate procedure I would suggest to postpone any operations with the result of compiling. The easiest (but not the best) way is to use simple timer:
var elt = $(this)[0];
var scrollHeight = elt.scrollHeight;
var scrollTop = elt.scrollTop;
var clientHeight = elt.clientHeight;
var downloadMore = scrollHeight - scrollTop - clientHeight < 50;
if (downloadMore) {
var childScope = scope.$new();
usersContainer = scope.displayPortion(usersContainer);
if (usersContainer) {
$compile(usersContainer)(childScope);
setTimeout(function() {
elt.scrollTop = 500;
});
}
}

Related

Multiple Morphs on Scroll

Struggling to get multiple SVGs to animate on scroll. I followed the CodePen and the first animation works fine, but the second doesn't. I need to be able to run like 4-5 of these on the homepage.
var scrollMorph1 = new TimelineLite({paused:true})
.to("#headerBottomMask",1, {morphSVG:"#headerBottomMask2"},0)
.to("#headerBottom01a",1, {morphSVG:"#headerBottom01b"},0)
.to("#headerBottom02a",1, {morphSVG:"#headerBottom02b"},0)
.to("#headerBottom03a",1, {morphSVG:"#headerBottom03b"},0)
.to("#headerBottom04a",1, {morphSVG:"#headerBottom04b"},0)
$(window).scroll(function() {
var scrolled = $(window).scrollTop();
var diff = 50;
var object1 = $('#header h1');
var topOfRange1 = object1.offset().top + diff;
if (scrolled > topOfRange1 ) {
scrollMorph1.play().timeScale(1);
} else {scrollMorph1.reverse().timeScale(1);}
});
var scrollMorph2 = new TimelineLite({paused:true})
.to("#clientsMask",1, {morphSVG:"#clientsMask2"},0)
$(window).scroll(function() {
var scrolled = $(window).scrollTop();
var diff = 50;
var object2 = $('#weare a');
var topOfRange2 = object2.offset().top + diff;
if (scrolled > topOfRange2 ) {
scrollMorph2.play().timeScale(1);
} else {scrollMorph2.reverse().timeScale(1);}
});
Code works fine. Wasn't targeting the Path, but rather the SVG's ID on the second one. Have to target the path.

How to add class to only one element that matches condition using jquery?

I'm trying to check if element crossed bottom edge of viewport. If it did, I want to add class start to this element. The problem is that when condition is satisfied class adds to all h2 elements.
Here is my code:
$.fn.checkAnimation = function() {
var context = this;
function isElementInViewport(elem) {
var $elem = context;
// Get the scroll position of the page.
var viewportTop = $(window).scrollTop();
var viewportBottom = viewportTop + $(window).height();
// Get the position of the element on the page.
var elemTop = Math.round( $elem.offset().top );
var elemBottom = elemTop + $elem.height();
return (elemTop < viewportBottom);
}
// Check if it's time to start the animation.
function checkAnimation() {
console.log(isElementInViewport($elem));
var $elem = context;
// If the animation has already been started
if ($elem.hasClass('start')) return;
if (isElementInViewport($elem)) {
// Start the animation
context.addClass('start');
}
}
checkAnimation();
return this;
};
$(window).on('scroll scrollstart touchmove orientationchange resize', function(){
$('h2').checkAnimation();
});
You'll need to change your checkAnimation jQuery plugin to loop through all elements in the jQuery object and process them individually or call your function like this
$('h2').each(function(){
$(this).checkAnimation();
}
Here is what I mean by processing the elements individually inside the plugin:
$.fn.checkAnimation = function() {
function isElementInViewport($elem) {
var viewportTop = $(window).scrollTop();
var viewportBottom = viewportTop + $(window).height();
var elemTop = Math.round( $elem.offset().top );
var elemBottom = elemTop + $elem.height();
return (elemTop < viewportBottom);
}
function checkAnimation() {
var $elem = $(this);
if ($elem.hasClass('start')) return;
if (isElementInViewport($elem)) {
$elem.addClass('start');
}
}
return this.each(checkAnimation);
};
If you use this version of the plugin you can call it like this:
$('h2').checkAnimation();
It will add the class only to the element that matches the condition not to all the element in the jQuery object you've called the function on.
Should be $elem.addClass('start'); instead and remove the var $elem = context; statement like :
function checkAnimation() {
console.log(isElementInViewport($elem));
// If the animation has already been started
if ($elem.hasClass('start')) return;
if (isElementInViewport($elem)) {
// Start the animation
$elem.addClass('start');
}
}
Hope this helps.
this inside a jQuery plugin is the jQuery object that contains the whole collection of elements represented by the previous selector/filter.
In order to treat each element in the collection as an individual instance you need to loop through the initial this.
Very basic pattern:
$.fn.pluginName = function(options){
// return original collection as jQuery to allow chaining
// loop over collection to access individual elements
return this.each(function(i, elem){
// do something with each element instance
$(elem).doSomething(); // elem === this also
});
}

Creating an endless scrollable div in a page

I'm trying to create an endless scrollable div in my page. To do so I'm trying to use the following code --> http://jsfiddle.net/cyrus2013/Qq85d/
$(document).ready(function(){
function lastAddedLiveFunc()
{
$('div#lastPostsLoader').html('<img src="../bigLoader.gif">');
$.get("loadmore.php", function(data){
if (data != "") {
//console.log('add data..');
$(".items").append(data);
}
$('div#lastPostsLoader').empty();
});
};
//lastAddedLiveFunc();
$(window).scroll(function(){
var wintop = $(window).scrollTop(), docheight = $(document).height(), winheight = $(window).height();
var scrolltrigger = 0.95;
if ((wintop/(docheight-winheight)) > scrolltrigger) {
//console.log('scroll bottom');
lastAddedLiveFunc();
}
});
});
But, here the code is for creating the same for an entire page (window) where as I need to create it for a specific 'div' in a webpage.
In this part of the page I have difficulty figuring out the dimensions of the 'div'.
var wintop = $(window).scrollTop(), docheight = $(document).height(), winheight = $(window).height();
var scrolltrigger = 0.95;
A similar thing would the "Ticker" section on Facebook on the right side of the page, which tells us what our friends are doing in real time.
Please help me in figure out the code for this requirement... Thanks in advance!
How about this (the randomPara stuff is there only to simulate the content):
DEMO
var scroller;
var initContents = 10;
var shown = 0;
var content = [];
function init(){
var scroller = document.getElementById('scroller');
for(var i=0;i<100;i++){
var randomPara = "";
var words = Math.floor(Math.random()*100);
for(var j=0;j<words;j++) randomPara += "word ";
content.push(randomPara+"<br>");
}
for(var i=0;i<initContents;i++){
scroller.innerHTML += content[shown];
shown++;
}
scroller.onscroll = function(){
if(shown < content.length) if(this.scrollTop >= this.scrollHeight-this.clientHeight)
scroller.innerHTML += content[shown];
shown++;
}
}

javascript code not working if placed at top of file

I have the following code. It set a filter bar in a search results page in a fixed position in the window after scrolling down to a certain point:
var docked;
var filters = document.getElementById('filters');
var init = filters.offsetTop;
function scrollTop() {
return document.body.scrollTop || document.documentElement.scrollTop;
}
window.onscroll = function () {
if (!docked && (init - scrollTop() < 0)) {
filters.style.top = 0;
filters.style.position = 'fixed';
filters.className = 'docked';
docked = true;
} else if (docked && scrollTop() <= init) {
filters.style.position = 'absolute';
filters.style.top = init + 'px';
filters.className = filters.className.replace('docked', '');
docked = false;
}
}
My issue is (and it's more curiosity) - if I place this code at the top of my file (in the <head>), it doesn't work at all. The filter section doesn't scroll with the window as it should. However, when I place this code at the bottom of the file (right above the closing </body> tag), it works just fine.
Why is this? Does this have something to do with the way the code works? Or could it be just a quirk or bug in the rest of my file causing this?
Wrap your assignments in window.onload = function(){ /* your code here */ }; and it will run. The reason being that your assignment of var filters = document.getElementById('filters'); comes back as undefined since that element does not exist during page load at the time you reference it.
Example:
var docked;
var filters;
var init;
window.onload = function(){
filters = document.getElementById('filters');
init = filters.offsetTop;
};
if you do this, it should work:
$(document).ready(window.onscroll = function () {
if (!docked && (init - scrollTop() < 0)) {
filters.style.top = 0;
filters.style.position = 'fixed';
filters.className = 'docked';
docked = true;
} else if (docked && scrollTop() <= init) {
filters.style.position = 'absolute';
filters.style.top = init + 'px';
filters.className = filters.className.replace('docked', '');
docked = false;
}
}
);

How to get an element's top position relative to the browser's viewport?

I want to get the position of an element relative to the browser's viewport (the viewport in which the page is displayed, not the whole page). How can this be done in JavaScript?
Many thanks
The existing answers are now outdated. The native getBoundingClientRect() method has been around for quite a while now, and does exactly what the question asks for. Plus it is supported across all browsers (including IE 5, it seems!)
From MDN page:
The returned value is a TextRectangle object, which contains read-only left, top, right and bottom properties describing the border-box, in pixels, with the top-left relative to the top-left of the viewport.
You use it like so:
var viewportOffset = el.getBoundingClientRect();
// these are relative to the viewport, i.e. the window
var top = viewportOffset.top;
var left = viewportOffset.left;
On my case, just to be safe regarding scrolling, I added the window.scroll to the equation:
var element = document.getElementById('myElement');
var topPos = element.getBoundingClientRect().top + window.scrollY;
var leftPos = element.getBoundingClientRect().left + window.scrollX;
That allows me to get the real relative position of element on document, even if it has been scrolled.
var element = document.querySelector('selector');
var bodyRect = document.body.getBoundingClientRect(),
elemRect = element.getBoundingClientRect(),
offset = elemRect.top - bodyRect.top;
Edit: Add some code to account for the page scrolling.
function findPos(id) {
var node = document.getElementById(id);
var curtop = 0;
var curtopscroll = 0;
if (node.offsetParent) {
do {
curtop += node.offsetTop;
curtopscroll += node.offsetParent ? node.offsetParent.scrollTop : 0;
} while (node = node.offsetParent);
alert(curtop - curtopscroll);
}
}
The id argument is the id of the element whose offset you want. Adapted from a quirksmode post.
jQuery implements this quite elegantly. If you look at the source for jQuery's offset, you'll find this is basically how it's implemented:
var rect = elem.getBoundingClientRect();
var win = elem.ownerDocument.defaultView;
return {
top: rect.top + win.pageYOffset,
left: rect.left + win.pageXOffset
};
function inViewport(element) {
let bounds = element.getBoundingClientRect();
let viewWidth = document.documentElement.clientWidth;
let viewHeight = document.documentElement.clientHeight;
if (bounds['left'] < 0) return false;
if (bounds['top'] < 0) return false;
if (bounds['right'] > viewWidth) return false;
if (bounds['bottom'] > viewHeight) return false;
return true;
}
source
The function on this page will return a rectangle with the top, left, height and width co ordinates of a passed element relative to the browser view port.
localToGlobal: function( _el ) {
var target = _el,
target_width = target.offsetWidth,
target_height = target.offsetHeight,
target_left = target.offsetLeft,
target_top = target.offsetTop,
gleft = 0,
gtop = 0,
rect = {};
var moonwalk = function( _parent ) {
if (!!_parent) {
gleft += _parent.offsetLeft;
gtop += _parent.offsetTop;
moonwalk( _parent.offsetParent );
} else {
return rect = {
top: target.offsetTop + gtop,
left: target.offsetLeft + gleft,
bottom: (target.offsetTop + gtop) + target_height,
right: (target.offsetLeft + gleft) + target_width
};
}
};
moonwalk( target.offsetParent );
return rect;
}
You can try:
node.offsetTop - window.scrollY
It works on Opera with viewport meta tag defined.
I am assuming an element having an id of btn1 exists in the web page, and also that jQuery is included. This has worked across all modern browsers of Chrome, FireFox, IE >=9 and Edge.
jQuery is only being used to determine the position relative to document.
var screenRelativeTop = $("#btn1").offset().top - (window.scrollY ||
window.pageYOffset || document.body.scrollTop);
var screenRelativeLeft = $("#btn1").offset().left - (window.scrollX ||
window.pageXOffset || document.body.scrollLeft);
Thanks for all the answers. It seems Prototype already has a function that does this (the page() function). By viewing the source code of the function, I found that it first calculates the element offset position relative to the page (i.e. the document top), then subtracts the scrollTop from that. See the source code of prototype for more details.
Sometimes getBoundingClientRect() object's property value shows 0 for IE. In that case you have to set display = 'block' for the element. You can use below code for all browser to get offset.
Extend jQuery functionality :
(function($) {
jQuery.fn.weOffset = function () {
var de = document.documentElement;
$(this).css("display", "block");
var box = $(this).get(0).getBoundingClientRect();
var top = box.top + window.pageYOffset - de.clientTop;
var left = box.left + window.pageXOffset - de.clientLeft;
return { top: top, left: left };
};
}(jQuery));
Use :
var elementOffset = $("#" + elementId).weOffset();
Based on Derek's answer.
/**
* Gets element's x position relative to the visible viewport.
*/
function getAbsoluteOffsetLeft(el) {
let offset = 0;
let currentElement = el;
while (currentElement !== null) {
offset += currentElement.offsetLeft;
offset -= currentElement.scrollLeft;
currentElement = currentElement.offsetParent;
}
return offset;
}
/**
* Gets element's y position relative to the visible viewport.
*/
function getAbsoluteOffsetTop(el) {
let offset = 0;
let currentElement = el;
while (currentElement !== null) {
offset += currentElement.offsetTop;
offset -= currentElement.scrollTop;
currentElement = currentElement.offsetParent;
}
return offset;
}
Here is something for Angular2 +. Tested on version 13
event.srcElement.getBoundingClientRect().top;

Categories

Resources