PageSourceSearch

https://jerrychen1990.github.io/js/back_to_top.js

js jerrychen1990.github.io collected 2026-10-03 09:22:57 UTC 4,938 bytes, 152 lines download raw bytes

1$(document).ready(() => {
2    const $button = $('#back-to-top');
3    const $footer = $('footer.footer');
4    const $mainColumn = $('.column-main');
5    const $leftSidebar = $('.column-left');
6    const $rightSidebar = $('.column-right');
7    let lastScrollTop = 0;
8    const rightMargin = 20;
9    const bottomMargin = 20;
10    let lastState = null;
11    const state = {
12        base: {
13            classname: 'card has-text-centered',
14            left: '',
15            width: 64,
16            bottom: bottomMargin
17        }
18    };
19    state['desktop-hidden'] = Object.assign({}, state.base, {
20        classname: state.base.classname + ' rise-up'
21    });
22    state['desktop-visible'] = Object.assign({}, state['desktop-hidden'], {
23        classname: state['desktop-hidden'].classname + ' fade-in'
24    });
25    state['desktop-dock'] = Object.assign({}, state['desktop-visible'], {
26        classname: state['desktop-visible'].classname + ' fade-in is-rounded',
27        width: 40
28    });
29    state['mobile-hidden'] = Object.assign({}, state.base, {
30        classname: state.base.classname + ' fade-in',
31        right: rightMargin
32    });
33    state['mobile-visible'] = Object.assign({}, state['mobile-hidden'], {
34        classname: state['mobile-hidden'].classname + ' rise-up'
35    });
36
37    function isStateEquals(prev, next) {
38        return ![].concat(Object.keys(prev), Object.keys(next)).some(key => {
39            return !Object.prototype.hasOwnProperty.call(prev, key)
40                || !Object.prototype.hasOwnProperty.call(next, key)
41                || next[key] !== prev[key];
42        });
43    }
44
45    function applyState(state) {
46        if (lastState !== null && isStateEquals(lastState, state)) {
47            return;
48        }
49        $button.attr('class', state.classname);
50        for (const prop in state) {
51            if (prop === 'classname') {
52                continue;
53            }
54            $button.css(prop, state[prop]);
55        }
56        lastState = state;
57    }
58
59    function isDesktop() {
60        return window.innerWidth >= 1078;
61    }
62
63    function isTablet() {
64        return window.innerWidth >= 768 && !isDesktop();
65    }
66
67    function isScrollUp() {
68        return $(window).scrollTop() < lastScrollTop && $(window).scrollTop() > 0;
69    }
70
71    function hasLeftSidebar() {
72        return $leftSidebar.length > 0;
73    }
74
75    function hasRightSidebar() {
76        return $rightSidebar.length > 0;
77    }
78
79    function getRightSidebarBottom() {
80        if (!hasRightSidebar()) {
81            return 0;
82        }
83        return Math.max.apply(null, $rightSidebar.find('.widget').map(function() {
84            return $(this).offset().top + $(this).outerHeight(true);
85        }));
86    }
87
88    function getScrollTop() {
89        return $(window).scrollTop();
90    }
91
92    function getScrollBottom() {
93        return $(window).scrollTop() + $(window).height();
94    }
95
96    function getButtonWidth() {
97        return $button.outerWidth(true);
98    }
99
100    function getButtonHeight() {
101        return $button.outerHeight(true);
102    }
103
104    function updateScrollTop() {
105        lastScrollTop = $(window).scrollTop();
106    }
107
108    function update() {
109        // desktop mode or tablet mode with only right sidebar enabled
110        if (isDesktop() || (isTablet() && !hasLeftSidebar() && hasRightSidebar())) {
111            let nextState;
112            const padding = ($mainColumn.outerWidth() - $mainColumn.width()) / 2;
113            const maxLeft = $(window).width() - getButtonWidth() - rightMargin;
114            const maxBottom = $footer.offset().top + (getButtonHeight() / 2) + bottomMargin;
115            if (getScrollTop() === 0 || getScrollBottom() < getRightSidebarBottom() + padding + getButtonHeight()) {
116                nextState = state['desktop-hidden'];
117            } else if (getScrollBottom() < maxBottom) {
118                nextState = state['desktop-visible'];
119            } else {
120                nextState = Object.assign({}, state['desktop-dock'], {
121                    bottom: getScrollBottom() - maxBottom + bottomMargin
122                });
123            }
124
125            const left = $mainColumn.offset().left + $mainColumn.outerWidth() + padding;
126            nextState = Object.assign({}, nextState, {
127                left: Math.min(left, maxLeft)
128            });
129            applyState(nextState);
130        } else {
131            // mobile and tablet mode
132            if (!isScrollUp()) {
133                applyState(state['mobile-hidden']);
134            } else {
135                applyState(state['mobile-visible']);
136            }
137            updateScrollTop();
138        }
139    }
140
141    update();
142    $(window).resize(update);
143    $(window).scroll(update);
144
145    $('#back-to-top').on('click', () => {
146        if (CSS && CSS.supports && CSS.supports('(scroll-behavior: smooth)')) {
147            window.scroll({ top: 0, behavior: 'smooth' });
148        } else {
149            $('body, html').animate({ scrollTop: 0 }, 400);
150        }
151    });
152});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.