简体   繁体   中英

Sticky sidebar with fixed header in Vue.js without jQuery

Following this answer to a similar question Make position: fixed behavior like sticky (for Vue2) , I have tried to implement it in my application.

The solution was a little bit buggy (in some cases it behaved oddly, especially when opening other tabs and coming back), so I decided to implement it using jQuery and it's working as expected.

Here is the working example:

<template>
    <div>
        <div class="recap">
            <div class="inner" :style="recapStyle">
            </div>
        </div>
    </div>
</template>
<script>
export default {
    name: 'ProductRecap',
    data() {
        return {
            scrollY: null,
            top: null,
            bottom: null,
            marginTop: 40,
            recapStyle: {},
        };
    },
    methods: {
        updatePosition(scroll) {
            // using jQuery to calculate amount
            const offset = $(this.$el).offset().top;
            const scrollAmount = offset - scroll;
            const rectHeight = $(this.$el).find('.inner').outerHeight();
            if (scrollAmount < this.top) {
                let updatedTop = scroll - offset + this.top;
                if ((scroll + rectHeight) < this.bottom) {
                    this.prevScroll = updatedTop;
                } else {
                    updatedTop = this.prevScroll;
                }
                this.$set(this.recapStyle, 'top', `${updatedTop}px`);
            } else {
                this.$delete(this.recapStyle, 'top');
            }
        },
    },
    watch: {
        scrollY(scrollUpdate) {
            // call `updatePosition` on scroll
            this.updatePosition(scrollUpdate);
        },
    },
    mounted() {
        // calculate header size (position: fixed) and add a fixed offset
        this.top = $('#main-header').outerHeight() + this.marginTop;
        // calculate height of the document (without the footer)
        this.bottom = document.querySelector('.global-container').offsetHeight;
        // update scrollY position
        window.addEventListener('scroll', _.throttle(() => {
            this.scrollY = Math.round(window.scrollY);
        }, 20, { leading: true }));
    },
};
</script>

However I'd like to find a solution that doesn't use jQuery to calculate the offset, so I headed to You Might Not Need jQuery , but if I just replace the offset part with the one that's suggested it's still a bit buggy.

$(el).offset();

Should become:

var rect = el.getBoundingClientRect();

{
  top: rect.top + document.body.scrollTop,
  left: rect.left + document.body.scrollLeft
}

So I replaced the line:

const offset = $(this.$el).offset().top;

with:

const rect = this.$el.getBoundingClientRect();
const offset = rect.top + document.body.scrollTop;

But the distance of the sidebar from the fixed header increases with the scroll: can anyone explain how to fix it?

Here is a working fiddle (slightly simplified): Fiddle

The short answer is to use these two lines (the first one is yours):

  const rect = this.$el.getBoundingClientRect();
  const offset = rect.top + window.pageYOffset;

The longer answer of course includes the thought process to achieve this result. I ran

  console.log($(this.$el).offset + "");

On your fiddle at the relevant place to see how the offset function is implemented and got this:

function( options ) {

        // Preserve chaining for setter
        if ( arguments.length ) {
            return options === undefined ?
                this :
                this.each( function( i ) {
                    jQuery.offset.setOffset( this, options, i );
                } );
        }

        var rect, win,
            elem = this[ 0 ];

        if ( !elem ) {
            return;
        }

        // Return zeros for disconnected and hidden (display: none) elements (gh-2310)
        // Support: IE <=11+
        // Running getBoundingClientRect on a
        // disconnected node in IE throws an error
        if ( !elem.getClientRects().length ) {
            return { top: 0, left: 0 };
        }

        // Get document-relative position by adding viewport scroll to viewport-relative gBCR
        rect = elem.getBoundingClientRect();
        win = elem.ownerDocument.defaultView;
        return {
            top: rect.top + win.pageYOffset,
            left: rect.left + win.pageXOffset
        };
    }

The solution is inspired from this line:

            top: rect.top + win.pageYOffset,

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM