7

I need to listen to an elements relative position. This is particular useful for dynamic layouting issues. There is no option to listen for CSS styles of even position() or offset() attributes.

Why: The relative position can change if a sibling change its dimension inside dialogs or forms (re-layout). So for interactive forms this can help to use i.e. the maximum available height or width of the parent or something like that.

mschmoock
  • 20,084
  • 6
  • 33
  • 35
  • What can make your elements position changed? Resizing window? There is an event for this one – A. Wolff Sep 11 '13 at 13:54
  • Not that your plugin is incorrect but I'm just wondering in what situation would you use this? What would you be doing inside the event handler? – Joe Sep 11 '13 at 14:01

3 Answers3

13

This custom jquery extension listens for object position by timer poll. I.m.o. there is no way to solve this without a timer, because position may be changed without jquery being invoked at all (i.e. re-layouting sibling elements). This solution watches the elements .position() and .offset() values. It can be easily improved to watch .css() styles or other stuff that is not covered by jQuery events.

jQuery extension:

jQuery.fn.onPositionChanged = function (trigger, millis) {
    if (millis == null) millis = 100;
    var o = $(this[0]); // our jquery object
    if (o.length < 1) return o;

    var lastPos = null;
    var lastOff = null;
    setInterval(function () {
        if (o == null || o.length < 1) return o; // abort if element is non existend eny more
        if (lastPos == null) lastPos = o.position();
        if (lastOff == null) lastOff = o.offset();
        var newPos = o.position();
        var newOff = o.offset();
        if (lastPos.top != newPos.top || lastPos.left != newPos.left) {
            $(this).trigger('onPositionChanged', { lastPos: lastPos, newPos: newPos });
            if (typeof (trigger) == "function") trigger(lastPos, newPos);
            lastPos = o.position();
        }
        if (lastOff.top != newOff.top || lastOff.left != newOff.left) {
            $(this).trigger('onOffsetChanged', { lastOff: lastOff, newOff: newOff});
            if (typeof (trigger) == "function") trigger(lastOff, newOff);
            lastOff= o.offset();
        }
    }, millis);

    return o;
};

Call it by:

$("#<SOME_ID>").onPositionChanged(function(){alert("foobar")});
mschmoock
  • 20,084
  • 6
  • 33
  • 35
  • 13
    this is really overkill – A. Wolff Sep 11 '13 at 13:55
  • 1
    BTW: `$(this[0])` is the same as `this` which refers to matched set of elements. `this` is already a jq object – A. Wolff Sep 11 '13 at 14:03
  • yeah, but you can't exec `this.position()` so I keep it as it is. – mschmoock Sep 11 '13 at 14:34
  • This saved me. I've linked this as an answer to my question, here: https://stackoverflow.com/questions/33096188/how-to-use-mutationobserver-or-similar-to-monitor-change-in-location-of-div-it/33108390#33108390 – Tom Oct 13 '15 at 16:57
  • I added the extension's script to one of the general JS code using ` – tarekahf Jul 25 '17 at 19:03
  • Oh ... it works now. I just noticed that the jQuery library was loaded twice. So that's why the plugin was deleted. – tarekahf Jul 26 '17 at 16:17
0

This is my solution which is based on willsteel solution. I had to monitor the changes to the width and offsetWidth. This will trigger the function even if the element has become invisible/visible due to any reason.

See updated code below:

jQuery.fn.onPositionChanged = function (trigger, millis) {
    if (millis == null) millis = 100;
    var o = $(this[0]); // our jquery object
    if (o.length < 1) return o;
    var lastPos = null;
    var lastOff = null;
    var lastWidth = null;
    var lastOffWidth = null;
    setInterval(function () {
        if (o == null || o.length < 1) return o; // abort if element is non existend eny more
        if (lastPos == null) lastPos = o.position();
        if (lastOff == null) lastOff = o.offset();
        if (lastWidth == null) lastWidth = o.width();
        if (lastOffWidth == null) lastOffWidth = o[0].offsetWidth;
        var newPos = o.position();
        var newOff = o.offset();
        var newWidth = o.width();
        var newOffWidth = o[0].offsetWidth;
        if (lastPos.top != newPos.top || lastPos.left != newPos.left) {
            $(this).trigger('onPositionChanged', { lastPos: lastPos, newPos: newPos });
            if (typeof (trigger) == "function") trigger(lastPos, newPos);
            lastPos = o.position();
        }
        if (lastOff.top != newOff.top || lastOff.left != newOff.left) {
            $(this).trigger('onPositionChanged', { lastOff: lastOff, newOff: newOff});
            if (typeof (trigger) == "function") trigger(lastOff, newOff);
            lastOff= o.offset();
        }
        if (lastWidth != newWidth) {
            $(this).trigger('onPositionChanged', { lastWidth: lastWidth, newWidth: newWidth});
            if (typeof (trigger) == "function") trigger(lastWidth, newWidth);
            lastWidth= o.width();
        }
        if (lastOffWidth != newOffWidth) {
            $(this).trigger('onPositionChanged', { lastOffWidth: lastOffWidth, newOffWidth: newOffWidth});
            if (typeof (trigger) == "function") trigger(lastOffWidth, newOffWidth);
            lastWidth= o.width();
        }
    }, millis);
    return o;
};
tarekahf
  • 738
  • 1
  • 16
  • 42
0

My variant of code above that will work on multiple objects and based on any selector, but it does not work with jQuery exactly.

var onPositionChanged = function (selector,trigger, millis) {
if (millis == null) millis = 200;
setInterval(function(){
    var o_s = jQuery(selector); // our jquery object
    if (o_s.length < 1) return o_s;

    for(var i = 0; i<o_s.length; i++)
    {
        var o = o_s.eq(i);

        var lastPos = jQuery(o).attr("lastPos");
        var lastOff = jQuery(o).attr("lastOff");
        lastPos = ((lastPos == "" || lastPos == undefined) ? null : JSON.parse(lastPos));
        lastOff = ((lastOff == "" || lastOff == undefined) ? null : JSON.parse(lastOff));
        setTimeout(function (o) {
            if (o == null || o.length < 1) return o; // abort if element is non existend eny more
            if (lastPos == null) lastPos = o.position();
            if (lastOff == null) lastOff = o.offset();
            var newPos = o.position();
            var newOff = o.offset();
            if (lastPos.top != newPos.top || lastPos.left != newPos.left) {
                jQuery(this).trigger('onPositionChanged', { lastPos: lastPos, newPos: newPos });
                if (typeof (trigger) == "function") trigger(o,lastPos, newPos);
                lastPos = o.position();
                jQuery(o).attr("lastPos",JSON.stringify(lastPos));
            }
            if (lastOff.top != newOff.top || lastOff.left != newOff.left) {
                jQuery(this).trigger('onOffsetChanged', { lastOff: lastOff, newOff: newOff});
                if (typeof (trigger) == "function") trigger(o,lastOff, newOff);
                lastOff= o.offset();
                jQuery(o).attr("lastOff",JSON.stringify(lastOff));
            }
        }, millis,o);
    }
},millis);

};

Usage example:

jQuery(document).ready(function(){
onPositionChanged(".grid-item:not(.d-none)",function(object){

    var left_percentage = parseInt(jQuery(object).position().left / jQuery(object).parent().width() * 100);
    var parent_width = jQuery(object).parent().width();

    var percentage_of_parent = parseInt(jQuery(object).width() / parent_width * 100);

    if(left_percentage > 74 && percentage_of_parent >= 25
    || left_percentage > 66 && percentage_of_parent >= 33
    || left_percentage > 49 && percentage_of_parent >= 50)
    {
        jQuery(object).find(".card").css("margin-right","0px");
        jQuery(object).parent().masonry('layout');
    }
    if(left_percentage < 1)
    {
        jQuery(object).find(".card").css("margin-left","0px");
        jQuery(object).parent().masonry('layout');
    }
});

});