Finding position of element within scrollable div - javascript

I have these "pages" aka div's inside a scrollable container. On command, I am trying to find out what part of the div in question, is touching the top of .pageContent.
So for example, right when the page loads, no part of #page_1 is touching the top of pageContent, but as I scroll down. #page_1 hits the top of .pageContent and I now want to figure out where that is.
I know I can get the position of .pageContent using $("#pageContent").scrollTop() but these page's could be different sizes and I am not sure how to go about figuring it out.
Could anyone put me in the right direction?
jsfiddle
HTML
<div id="pageContent">
<div id="page_1" class="content"></div>
<div id="page_2" class="content"></div>
<div id="page_3" class="content"></div>
</div>
CSS
#pageContent {
overflow: auto;
width:500px;
height:300px;
padding:10px;
border:1px solid black;
background-color:grey;
}
.content {
height:400px;
width:300px;
margin:0 auto;
background-color:red;
margin-bottom:10px;
}

You can use the jQuery .position() function to compute where each page is in relation to the top of the container. See this Fiddle.
For example, for #page_1,
var page1 = $('#page_1');
$('#pageContent').scroll(function() {
// page1.position().top gives the position of page_1 relative to the
// top of #pageContent
});

ScrollTop can be used, be I wouldn't recommend it.
Attach a scroll event to your main div and listener for all the objects inside:
$('#pageContent').scroll(function(){
var pages = $("#pageContent > .content");
for (var i = 0; i < pages.length; i++)
{
if ($(pages[i]).position().top < 0 && ( $(pages[i]).position().top + $(pages[i]).outerHeight() ) > 0)
{
var outerHeight = $(pages[i]).outerHeight();
var pixels = (outerHeight - (outerHeight + $(pages[i]).position().top));
console.log("These pixels are in view between: " + pixels + " and " + outerHeight );
}
}
})
Every time the div scroll a loop is performed checking the position of all elements. If the elements scroll out of view a the top the if is triggered, calculating the remaining visible pixels of the page currently visible.
This uses jQuery's: position() and outerHeight() and JavaScript's native offsetTop.
http://jsfiddle.net/q5aaLo9L/4/

I tried something like this
$(document).ready(function () {
var divs = $('.content').map(function (i, el) {
return $(el).offset().top - $(el).parent().offset().top;
});
$('#pageContent').scroll(function () {
var index = findIndex($(this).scrollTop(), divs) - 1;
if (index > -1) {
console.log($(this).children().eq(index).attr('id'));
} else {
console.log('outside');
}
});
});
function findIndex(pos, divs) {
return (divs.filter(function (el, et) {
return et <= pos
}).length);
}
It's not super clean code because I had to do it quickly.
DEMO
I hope this helps

I mocked this up, it uses JQuery's each() function to iterate through the pages and return the information of the page that has breached the top of the box.
I wasn't sure from your question exactly what you wanted returned, so I got it to return either the percentage of the page that has cleared the top border, the position (as negative value of pixels) of the top of the "page " in relation to the content container, and also just the ID of that div.
var getCurrentPage = function(){
var page;
var position;
var percentageRead;
$('.content').each(function(){
if($(this).position().top <= 0){
page = $(this);
position = $(this).position().top;
}
});
percentageRead = ((position *-1)/ $(page).height()* 100);
console.log(page.attr('id'));
console.log(position);
console.log(percentageRead + '%');
}
$('#pageContent').on('scroll', getCurrentPage);
You could fire this on any event but I used scroll to build it.

Related

How can i trigger a css class when a reach to a specific div with scroll

i am new to Javascript and i would like your help.
I have a code and when you scroll from the top of a page changes css of a class and zooms the images.
What i would like to do is, to put a class in a div ( lets say .start) and when i reach to that class then to start zoom the image.
$(window).scroll(function () {
var scroll = $(window).scrollTop();
$(".zoom").css({
backgroundSize: (100 + scroll / 20) + "%"
});
});
Check this: getBoundingClientRect()
HTML
<div class="wrap">
<div class="content">A</div>
<div class="content">B</div>
<div id="start" class="content">C</div>
<div class="zoom"><!-- Image --></div>
</div>
JS
$(window).scroll(function () {
var target = $('#start')[0].getBoundingClientRect(); // id="start" Element
// Start zoom at #start element's position
if (target.top - target.height <= 0) {
$(".zoom").css({
backgroundSize: (100 + (target.height - target.top) / 20) + "%"
});
}
});
getBoundingClientRect() is returns the size of an element and its position relative to the viewport.
You can check the target Element's position and trigger it.
Demo: https://jsfiddle.net/ghlee/uecb26ft
I guess this question is similar to your problem?
You will need to add your zoom class to the element via .addClass("zoom"); when it is in the viewport.
Well not that exactly.
i have this
$(window).scroll(function () {
$('#currentContent').bind('inview', monitor);
function monitor(event, visible)
{
if(visible)
{
$(window).scroll(function () {
var scroll = $(window).scrollTop();
$(".zoom").css({
backgroundSize: (100 + scroll / 20) + "%"
});
});
}
else
{
// element has gone out of the viewport
}
}
});
The "problem" is var scroll = $(window).scrollTop();
I want the scroll variable to start not from TOP but from the div where i have add a class.
I have tried this var scroll = $('.currentContent').offset().top;
But nothing happens
To determine if an element with the desired class is in the viewport (i.e., been scrolled to), you could calculate the offset of the .start element and compare it with the current scroll value on scroll.
If the element is in the viewport, you could then zoom the image. The below snippet will increase the image size when you reach it. Just zoom past the height of the red area. If you scroll back up, it should get smaller. You may want a different effect, but the same idea applies.
Also, see my comments in the inViewport function.
$(window).scroll(function() {
if (inViewport($(".start"))) {
$(".start").css("height", "200px").css("width", "200px");
} else {
$(".start").css("height", "50px").css("width", "50px");
}
});
function inViewport(element) {
// added extra height so you can see the element change back to smaller size
// you could remove this value if you just want the element to change size when out of the viewport
const extra = 50;
var elementTop = element.offset().top + extra;
var elementBottom = elementTop + element.outerHeight();
var viewportTop = $(window).scrollTop();
var viewportBottom = viewportTop + $(window).height();
return elementBottom > viewportTop && elementTop < viewportBottom;
};
#placeholder {
height: 300px;
background-color: red;
}
.start {
height: 50px;
width: 50px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="placeholder"></div>
<img class="start" src="https://homepages.cae.wisc.edu/~ece533/images/airplane.png" />
This is what Intersection Observer was made to do. With this you can watch elements and react when they come into view or intersect with each other.
First you set the options for the IO:
let options = {
rootMargin: '0px',
threshold: 1.0
}
let observer = new IntersectionObserver(callback, options);
If you leave out the root option it wall default to your browser window, so once an element that you watch comes into viewport, the callback function gets triggered.
Then you specify which elements you want to observe:
let target = document.querySelector('.zoom');
observer.observe(target);
Here we now watch every element with the class zoom.
Last step is to define the callback function:
let callback = (entries, observer) => {
entries.forEach(entry => {
// Each entry describes an intersection change for one observed
// target element:
// like: $(element).addClass('zoom');
});
};
You can unobserve an element if you don't need it anymore, for example if you only want to play the zoom animation only once when it comes into view.
Also check out this example on how to change bg-color depending on how much of an element is visible on screen.
Edit: Maybe this example is more fitting (see comments) because it actually adds a class when user scrolls to the element.
Last point, you can use this polyfill from w3c to support older browsers.
Well, you seem to be on the right path. You probably in addition need to pay attention to the background-size property. Instead of the JavaScript strongly named backgroundSize for the css background-size property, accessed through the style object of an element like this; currentElement.style.backgroundSize, you might want to do this; "background-size": (100 + scroll / 20) + "%. Here's a working example that builds upon your existing code:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!DOCTYPE html>
<html>
<head>
<title>My page</title>
</head>
<body>
<h1 id="start">My Zoom Heading</h1>
<p class="zoom">My zoom area</p>
<script>
$(window).scroll(function() {
var scroll = $(window).scrollTop();
var zoomStart = $("#start").offset().top;
if (scroll >= zoomStart) {
$(".zoom").css({
"background-size": (100 + scroll / 10) + "%"
});
}
});
</script>
<style>
#start {
margin-top: 60px;
}
.zoom {
margin-top: 10px;
margin-bottom: 300px;
width: 300px;
height: 300px;
background: url('{some-image-url-here}') no-repeat;
}
</style>
</body>
</html>

Using .outerWidth() & .outerHeight() with the 'resize' event to center a child element in parent

I am attempting to write some JavaScript code that will allow me to center a child element within it's parent using padding. Then using the same function to recalculate the spacing using the 'resize' event. Before you start asking me why i am not doing this with CSS, this code is only a small part of a larger project. I have simplified the code as the rest of the code works and would only serve to confuse the subject.
Calculating the space - This is the function that caculates the amount of space to be used on either side of the child element.
($outer.outerWidth() - $inner.outerWidth()) / 2;
($outer.outerHeight() - $inner.outerHeight()) / 2;
The problem
Although i have successfully managed to get the desired results with margin. Padding is causing me problems.
It appears to be increasing the width on the outer element when resized
It does not center the child element perfectly (there appears to be an offset)
The inner element collapses on resize and becomes invisible.
I realize that there may be some fundamentals regarding padding that are causing my problems however after numerous console logs and observing the data returned i still can't put my finger on the problem. Any suggestion would be very welcome. It may turn out that this is not feasible at all.
HTML
<div id="demo" class="outer">
<div class="inner">
</div>
</div>
CSS
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
.outer {
width:97%;
height:400px;
border:1px solid black;
margin:20px;
}
.inner {
width:40%;
height:100px;
background-color:grey;
}
JAVASCRIPT
var $outer = $(".outer");
var $inner = $(".inner");
var getSpace = function(axis) {
if (axis.toLowerCase() == "x") {
return ($outer.outerWidth() - $inner.outerWidth()) / 2;
} else if (axis.toLowerCase() == "y") {
return ($outer.outerHeight() - $inner.outerHeight()) / 2;
}
}
var renderStyle = function(spacingType) {
var lateralSpace = getSpace("x");
var verticalSpace = getSpace("y");
var $element;
if (spacingType == "padding") {
$element = $outer;
} else if (spacingType == "margin") {
$element = $inner;
}
$.each(["top", "right", "bottom", "left"], function(index, direction) {
if (direction == "top" || direction == "bottom") {
$element.css(spacingType + "-" + direction, verticalSpace);
}
else if (direction == "right" || direction == "left") {
$element.css(spacingType + "-" + direction, lateralSpace);
}
});
};
var renderInit = function() {
$(document).ready(function() {
renderStyle("padding");
});
$(window).on("resize", function() {
renderStyle("padding");
});
}
renderInit();
EXAMPLE - link
Although I completely disagree with this approach to horizontally centring an element, hopefully this will help you on your way.
Fiddle: https://jsfiddle.net/0uxx2ujg/
JavaScript:
var outer = $('.outer'), inner = $('.inner');
function centreThatDiv(){
var requiredPadding = outer.outerWidth() / 2 - (inner.outerWidth() / 2);
console.log(requiredPadding);
outer.css('padding', '0 ' + requiredPadding + 'px').css('width','auto');
}
$(document).ready(function(){
// fire on page load
centreThatDiv();
});
$(window).resize(function(){
// fire on window resize
centreThatDiv();
});
HTML:
<div class="outer">
<div class="inner">Centre me!</div>
</div>
CSS:
.outer{ width:80%; height:300px; margin:10%; background: tomato; }
.inner{ width:60px; height:60px; background:white; }
Furthered on from why I disagree with this approach - JavaScript shouldn't be used to lay things out. Sure - it can be, if it really needs to be used; but for something as simple as centring an element, it's not necessary at all. Browsers handle resizing CSS elements themselves, so by using JS you introduce more headaches for yourself further down the line.
Here's a couple of examples of how you can achieve this in CSS only:
text-align:center & display:inline-block https://jsfiddle.net/0uxx2ujg/1/
position:absolute & left:50% https://jsfiddle.net/0uxx2ujg/2/ (this can be used for vertically centring too which is trickier than horizontal)
You can create the new CSS class to adjust for elements size on $window.onresize = function () {
//add your code
};

Get the first Element that is floating on next line

I have a list of Elements that are floating left:
<style>
ul {
overflow: hidden;
width: 100%;
}
ul li {
float: left;
width: 100px;
}
</style>
<ul>
<li id="#one">One</li>
<li id="#two">Two</li>
<li id="#three">Three</li>
...
</ul>
...having a window width of 200px the first Element that is floating on the second line would be #three... having a window width of just 100px that would be #two.
Now on every <li> there is a click-event bound, that should tell me which Element is the first Element on the next Line, seen by itself.
Clicking on #one with a window width of 200px I want to get #three, and with a window width of 100px I want to get #two.
I tried to solve this by getting the position of the Element that is clicked on, query the Position inside the window using .getBoundingClientRect(), get the height of the Element (taking 20px here), and then get the Element by using document.elementFromPoint(y,x)...something like this:
y = clicked_element.getBoundingClientRect().left
x = clicked_element.getBoundingClientRect().top + 21
first_element_on_next_line = document.elementFromPoint(y,x)
so far, so good... but of course this is just working if the first element on the next line is really inside the window! :-\
and this is where I'm stuck...if I have a window width of 200px and a window height of just 20px (just for the example), I just can't select #three with document.elementFromPoint() because its position is outside the window.
I could also use jQuery, it's included anyways, but everything came across was using document.elementFromPoint()
Any Ideas??
You could use following logic using jQuery .position() method: {as suggested by maja}
$(function () {
var $lis = $('ul li').on('click', function(){
var $nextFirstElement = $(this).nextAll('.first').first();
console.log($nextFirstElement);
});
$(window).on('resize', function () {
var initialLeft;
$lis.each(function(i){
if (i === 0) { // this could be put out of the loop, but...
initialLeft = $(this).position().left;
}
$(this).toggleClass('first', i === 0 || $(this).position().left === initialLeft);
});
}).resize();
});
-jsFiddle-
The Solution I'm using now (which in fact is the Solution charlietfl brought up in the Comments) is this:
$(function(){
var list = $("ul"),
lis = list.children(),
first_li = lis.first(),
list_width, first_li_width, items_per_line
var set_vars = function(){
list_width = list.width()
first_li_width = first_li.width()
items_per_row = Math.floor(list_width / first_li_width)
}
set_vars()
lis.click(function(){
index = lis.index(this)
next_first_index = index + (items_per_row - (index % items_per_row))
console.log(next_first_index)
})
$(window).resize(function(){
set_vars()
})
})

Scroll event background change

I am trying to add a scroll event which will change the background of a div which also acts as the window background (it has 100% width and height). This is as far as I get. I am not so good at jquery. I have seen tutorials with click event listeners. but applying the same concept , like, returning scroll event as false, gets me nowhere. also I saw a tutorial on SO where the person suggest use of array. but I get pretty confused using arrays (mostly due to syntax).
I know about plugins like waypoints.js and skrollr.js which can be used but I need to change around 50-60 (for the illusion of a video being played when scrolled) ... but it wont be feasible.
here is the code im using:-
*
{
border: 2px solid black;
}
#frame
{
background: url('1.jpg') no-repeat;
height: 1000px;
width: 100%;
}
</style>
<script>
$(function(){
for ( i=0; i = $.scrolltop; i++)
{
$("#frame").attr('src', ''+i+'.jpg');
}
});
</script>
<body>
<div id="frame"></div>
</body>
Inside your for loop, you are setting the src attribute of #frame but it is a div not an img.
So, instead of this:
$("#frame").attr('src', ''+i+'.jpg');
Try this:
$("#frame").css('background-image', 'url(' + i + '.jpg)');
To bind a scroll event to a target element with jQuery:
$('#target').scroll(function() {
//do stuff here
});
To bind a scroll event to the window with jQuery:
$(window).scroll(function () {
//do stuff here
});
Here is the documentation for jQuery .scroll().
UPDATE:
If I understand right, here is a working demo on jsFiddle of what you want to achieve.
CSS:
html, body {
min-height: 1200px; /* for testing the scroll bar */
}
div#frame {
display: block;
position: fixed; /* Set this to fixed to lock that element on the position */
width: 300px;
height: 300px;
z-index: -1; /* Keep the bg frame at the bottom of other elements. */
}
Javascript:
$(document).ready(function() {
switchImage();
});
$(window).scroll(function () {
switchImage();
});
//using images from dummyimages.com for demonstration (300px by 300px)
var images = ["http://dummyimage.com/300x300/000000/fff",
"http://dummyimage.com/300x300/ffcc00/000",
"http://dummyimage.com/300x300/ff0000/000",
"http://dummyimage.com/300x300/ff00cc/000",
"http://dummyimage.com/300x300/ccff00/000"
];
//Gets a valid index from the image array using the scroll-y value as a factor.
function switchImage()
{
var sTop = $(window).scrollTop();
var index = sTop > 0 ? $(document).height() / sTop : 0;
index = Math.round(index) % images.length;
//console.log(index);
$("#frame").css('background-image', 'url(' + images[index] + ')');
}
HTML:
<div id="frame"></div>
Further Suggestions:
I suggest you change the background-image of the body, instead of the div. But, if you have to use a div for this; then you better add a resize event-istener to the window and set/update the height of that div with every resize. The reason is; height:100% does not work as expected in any browser.
I've done this before myself and if I were you I wouldn't use the image as a background, instead use a normal "img" tag prepend it to the top of your page use some css to ensure it stays in the back under all of the other elements. This way you could manipulate the size of the image to fit screen width better. I ran into a lot of issues trying to get the background to size correctly.
Html markup:
<body>
<img src="1.jpg" id="img" />
</body>
Script code:
$(function(){
var topPage = 0, count = 0;
$(window).scroll( function() {
topPage = $(document).scrollTop();
if(topPage > 200) {
// function goes here
$('img').attr('src', ++count +'.jpg');
}
});
});
I'm not totally sure if this is what you're trying to do but basically, when the window is scrolled, you assign the value of the distance to the top of the page, then you can run an if statement to see if you are a certain point. After that just simply change run the function you would like to run.
If you want to supply a range you want the image to change from do something like this, so what will happen is this will allow you to run a function only between the specificied range between 200 and 400 which is the distance from the top of the page.
$(function(){
var topPage = 0, count = 0;
$(window).scroll( function() {
topPage = $(document).scrollTop();
if(topPage > 200 && topPage < 400) {
// function goes here
$('#img').attr('src', ++count +'.jpg');
}
});
});

Scrollpane on the bottom, css is hacky, javascript is hard

I want to put a bar on the bottom of my page containing a varying number of pictures, which (if wider than the page) can be scrolled left and right.
The page width is varying, and I want the pane to be 100% in width.
I was trying to do a trick by letting the middle div overflow and animate it's position with jquery.animate().
Like this:
Here is a fiddle without the js: http://jsfiddle.net/SoonDead/DdPtv/7/
The problems are:
without declaring a large width to the items holder it will not overflow horizontally but vertically. Is this a good hack? (see the width: 9000px in the fiddle)
I only want to scroll the middle pane if it makes sense. For this I need to calculate the width of the overflowing items box (which should be the sum of the items' width inside), and the container of it with the overflow: hidden attribute. (this should be the width of the browser window minus the left and right buttons).
Is there a way to calculate the length of something in js without counting all of it's childrens length manually and sum it up?
Is there a way to get the width of the browser window? Is there a way to get a callback when the window is resized? I need to correct the panes position if the window suddenly widens (and the items are in a position that should not be allowed)
Since the window's width can vary I need to calculate on the fly if I can scroll left or right.
Can you help me with the javascript?
UPDATE: I have a followup question for this one: Scroll a div vertically to a desired position using jQuery Please help me solve that one too.
Use white-space:nowrap on the item container and display:inline or display:inline-block to prevent the items from wrapping and to not need to calculate or set an explicit width.
Edit:: Here's a live working demo: http://jsfiddle.net/vhvzq/2/
HTML
<div class="hscroll">
<ol>
<li>...</li>
<li>...</li>
</ol>
<button class="left"><</button>
<button class="right">></button>
</div>
CSS
.hscroll { white-space:nowrap; position:relative }
.hscroll ol { overflow:hidden; margin:0; padding:0 }
.hscroll li { list-style-type:none; display:inline-block; vertical-align:middle }
.hscroll button { position:absolute; height:100%; top:0; width:2em }
.hscroll .left { left:0 }
.hscroll .right { right:0 }
JavaScript (using jQuery)
$('.hscroll').each(function(){
var $this = $(this);
var scroller = $this.find('ol')[0];
var timer,offset=15;
function scrollLeft(){ scroller.scrollLeft -= offset; }
function scrollRight(){ scroller.scrollLeft += offset; }
function clearTimer(){ clearInterval(timer); }
$this.find('.left').click(scrollLeft).mousedown(function(){
timer = setInterval(scrollLeft,20);
}).mouseup(clearTimer);
$this.find('.right').click(scrollRight).mousedown(function(){
timer = setInterval(scrollRight,20);
}).mouseup(clearTimer);
});
Thanks Phrogz for this part -- give the image container the white-space: nowrap; and display: inline-block;.
You can calculate the width without having to calculate the width of the children every time but you will need to calculate the width of the children once.
//global variables
var currentWidth = 0;
var slideDistance = 0;
var totalSize = 0;
var dispWidth = (winWidth / 2); //this should get you the middle of the page -- see below
var spacing = 6; //padding or margins around the image element
$(Document).Ready(function() {
$("#Gallery li").each(function () {
totalSize = totalSize + parseFloat($(this).children().attr("width"));// my images are wrapped in a list so I parse each li and get it's child
});
totalSpacing = (($("#Gallery li").siblings().length - 1) * spacing); //handles the margins between pictures
currentWidth = (parseFloat($("#Gallery li.pictureSelected").children().attr("width")) + spacing);
maxLeftScroll = (dispWidth - (totalSize + totalSpacing)); //determines how far left you can scroll
});
function NextImage() {
currentWidth = currentWidth + (parseFloat($("#Gallery li.pictureSelected").next().children().attr("width")) + spacing); //gets the current width plus the width of the next image plus spacing.
slideDistance = (dispWidth - currentWidth)
$("#Gallery").animate({ left: slideDistance }, 700);
}
There is a way to get the browser window with in javascript (jQuery example).
and there is a way to catch the resize event.
var winWidth = $(window).width()
if (winWidth == null) {
winWidth = 50;
}
$(window).resize(function () {
var winNewWidth = $(window).width();
if (winWidth != winNewWidth) {
window.clearTimeout(timerID);
timerID = window.setInterval(function () { resizeWindow(false); }, 100);
}
winWidth = winNewWidth;
});
On my gallery there's actually quite a bit more but this should get you pointed in the right direction.
You need to change your #items from
#items
{
float: left;
background: yellow;
width: 9000px;
}
to
#items {
background: yellow;
}
Then calculate the width very easily with jQuery
// #items width is calculated as the number of child .item elements multiplied by their outerWidth (width+padding+border)
$("#items").width(
$(".item").length * $(".item").outerWidth()
);
and simply declare click events for the #left and #right elements
$("#left").click(function() {
$("#middle").animate({
scrollLeft: "-=50px"
}, 'fast');
});
$("#right").click(function() {
$("#middle").animate({
scrollLeft: "+=50px"
}, 'fast');
});
jsFiddle link here
EDIT
I overlooked that detail about the varying image widths. Here is the correct way to calculate the total width
var totalWidth = 0;
$(".item").each(function(index, value) {
totalWidth += $(value).outerWidth();
});
$("#items").width(totalWidth);

Categories

Resources