Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Finding where element meets top of scrollable div

I have a scrollable div container fits multiple "pages" (or div's) inside of it's container.

My goal is to, at any given moment, figure out where inside my red container does it reach the top of my scrollable container. So it can be a constant on scroll event, or a button that triggers this task.

So for example. If I have a absolute div element inside one of my red boxes at top:50px. And if I scroll to where that div element reaches the top of my scrollable container. The trigger should say that I am at 50px of my red container.

I'm having a hard time grasping how to accomplish this but I've tried things like:

$("#pageContent").scroll(function(e) {
    console.log($(this).scrollTop());   
});

But it doesn't take into account the separate pages and I don't believe it it completely accurate depending on the scale. Any guidance or help would be greatly appreciated.

Here is my code and a jsfiddle to better support my question.

Note: If necessary, I use scrollspy in my project so I could target which red container needs to be checked.

HTML

<div id="pageContent" class="slide" style="background-color: rgb(241, 242, 247); height: 465px;">
    <div id="formBox" style="height: 9248.627450980393px;">
        <div class="trimSpace" style="width: 1408px; height: 9248.627450980393px;">
            <div id="formScale" style="width: 816px; -webkit-transform: scale(1.7254901960784315); display: block;">
                <form action="#" id="XaoQjmc0L51z_form" autocomplete="off">
                    <div class="formContainer" style="width:816px;height:1056px" id="xzOwqphM4GGR_1">
                        <div class="formContent">
                            <div class="formBackground">
                                <div style="position:absolute;top:50px;left:100px;width:450px;height:25px;background-color:#fff;color:#000;">When this reaches the top, the "trigger" should say 50px"</div>
                            </div>
                        </div>
                    </div>
                    <div class="formContainer" style="width:816px;height:1056px" id="xzOwqphM4GGR_2">
                        <div class="formContent">
                            <div class="formBackground"><div style="position:absolute;top:50px;left:100px;width:450px;height:25px;background-color:#fff;color:#000;">This should still say 50px</div></div>
                        </div>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>

CSS

#pageContent {
    position:relative;
    padding-bottom:20px;
    background-color:#fff;
    z-index:2;
    overflow:auto;
    -webkit-overflow-scrolling: touch;
    -webkit-transform: translate(0, 0);
    -moz-transform: translate(0, 0);
    -ms-transform: translate(0, 0);
    transform: translate(0, 0);
}
#formBox {
    display: block;
    position: relative;
    height: 100%;
    padding: 15px;
}
.trimSpace {
    display: block;
    position: relative;
    margin: 0 auto;
    padding: 0;
    height: 100%;
    overflow: hidden;
}
#formScale::after {
    display: block;
    content:'';
    padding-bottom:5px;
}
#formScale {
    position:relative;
    width:816px;
    margin:0;
    -webkit-transform-origin: 0 0;
    -moz-transform-origin: 0 0;
    transform-origin: 0 0;
    -ms-transform-origin: 0 0;
}
.formContainer {
    position:relative;
    margin : 0 auto 15px auto;
    padding:0;
}
.formContent {
    position:absolute;
    width:100%;
    height:100%;
}
.formBackground {
    width:100%;
    height:100%;
    background-color:red;
}

JS

var PAGEWIDTH = 816;

$(window).resize(function (e) {
    zoomProject();
    resize();
});

function resize() {
    $("#pageContent").css('height', window.innerHeight - 45 + 'px');
}

function zoomProject() {
    var maxWidth = $("#formBox").width(),
        percent = maxWidth / PAGEWIDTH;
    $("#formScale").css({
        'transform': 'scale(' + percent + ')',
        '-moz-transform': 'scale(' + percent + ')',
        '-webkit-transform': 'scale(' + percent + ')',
        '-ms-transform': 'scale(' + percent + ')'
    });
    $(".trimSpace").css('width', (PAGEWIDTH * percent) + 'px');
    $("#formBox, .trimSpace").css('height', ($("#formScale").height() * percent) + 'px');
}

zoomProject();
resize();

EDIT:

I don't think I am conveying a good job at relaying what I want to accomplish.

At the moment there are two .formContainer's. When I scroll #pageContainer, the .formContainer divs move up through #pageContainer.

So what I want to accomplish is, when a user clicks the "ME" button or #click (as shown in the fiddle below), I'd like to know where in that particular .formContainer, is it touching the top of #pageContainer.

I do use scroll spy in my real world application so I know which .formContainer is closest to the top. So if you just want to target one .formContainer, that is fine.

I used these white div elements as an example. If I am scrolling #pageContainer, and that white div element is at the top of screen as I am scrolling and I click on "ME", the on click trigger should alert to me that .formContainer is touching the top of #pageContainer at 50px from the top. If, the the red container is just touching the top of #pageContainer, it should say it is 0px from the top.

I hope that helps clear up some misconception.

Here is an updated jsfiddle that shows the kind of action that I want to happen.

like image 764
bryan Avatar asked Mar 04 '15 04:03

bryan


2 Answers

I am giving this a stab because I find these things interesting. It might just be a starting point since I have a headache today and am not thinking straight. I'd be willing to bet it can be cleaned up and simplified some.

I also might be over-complicating the approach I took, getting the first visible form, and the positioning. I didn't use the getBoundingClientRect function either.

Instead, I approached it trying to account for padding and margin, using a loop through parent objects up to the pageContent to get the offset relative to that element. Because the form is nested a couple levels deep inside the pageContent element you can't use position(). You also can't use offset() since that changes with scroll. The loop approach allowed me to factor the top margin/padding in. I haven't looked at the other solutions proposed fully so there might be a shorter way to accomplish this.

Keeping in mind that the scale will affect the ACTUAL location of the child elements, you have to divide by your scale percentage when getting the actual location. To do that I moved the scalePercentage to a global var so it was usable by the zoom function and the click.

Here's the core of what I did. The actual fiddle has more logging and junk:

var visForm = getVisibleForm();
var formTop = visForm.position().top;

var parents = visForm.parentsUntil('#pageContent');
var truOffset = 0;
parents.each(function() {
    truOffset -= $(this).position().top;
});
// actual location of form relative to pageContent visible pane
var formLoc = truOffset - formTop;   
var scaledLoc = formLoc / scalePercent;

Updated Fiddle (forgot to account for scale in get func): http://jsfiddle.net/e6vpq9c8/5/

like image 171
Daved Avatar answered Oct 17 '22 23:10

Daved


If I understand your question correctly, what you want is to catch when certain descendant elements reach the top of the outer container, and then determine the position of the visible "page" (div with class formContainer) relative to the top.

If so, the first task is to mark the specific elements that could trigger this:

<div class='triggerElement' style="position:absolute;top:50px;left:100px;width:450px;height:25px;background-color:#fff;color:#000;">When this reaches the top, the "trigger" should say 50px"</div>

Then the code:

// arbitrary horizontal offset - customize for where your trigger elements are placed horizontally
var X_OFFSET = 100;

// determine once, at page load, where outer container is on the page
var outerContainerRect;
$(document).ready(function() {
    outerContainerRect = $("#pageContent").get(0).getBoundingClientRect();
});

// when outer container is scrolled
$("#pageContent").scroll(function(e) {
    // determine which element is at the top
    var topElement = $(document.elementFromPoint(outerContainerRect.left+X_OFFSET, outerContainerRect.top));
    /*
    // if a trigger element
    if (topElement.hasClass("triggerElement")) {
        // get trigger element's position relative to page
        console.log(topElement.position().top);
    }
    */

    var page = topElement.closest(".formContainer");
    if (page.length > 0) {
        console.log(-page.get(0).getBoundingClientRect().top);
    }

});

EDIT: Changed code to check formContainer elements rather than descendant elements, as per your comment.

http://jsfiddle.net/j6ybgf58/23/

EDIT #2: A simpler approach, given that you know which formContainer to target:

$("#pageContent").scroll(function(e) {
    console.log($(this).scrollTop() - $("#xzOwqphM4GGR_1").position().top);
});

http://jsfiddle.net/rL4Ly3yy/5/

However, it still gives different results based on the size of the window. This seems unavoidable - the zoomProject and resize functions are explicitly resizing the content, so you would have to apply the inverse transforms to the number you get from this code if you want it in the original coordinate system.

like image 1
radiaph Avatar answered Oct 18 '22 01:10

radiaph