Skip to content

Commit 5f7cf57

Browse files
stevenjoezhang1v9
authored andcommitted
Reimplement Bootstrap Affix (theme-next#1130)
1 parent 5ef48f7 commit 5f7cf57

File tree

2 files changed

+72
-119
lines changed

2 files changed

+72
-119
lines changed

layout/_macro/sidebar.swig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</div>
88
</div>
99

10-
<aside id="sidebar" class="sidebar">
10+
<aside class="sidebar">
1111
<div class="sidebar-inner">
1212

1313
{%- set display_toc = theme.toc.enable and display_toc %}

source/js/schemes/pisces.js

Lines changed: 71 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,132 +1,85 @@
11
/* global CONFIG */
22

3-
($ => {
4-
class Affix {
5-
constructor(element, options) {
6-
this.options = $.extend({
7-
offset: 0,
8-
target: window
9-
}, options);
10-
this.$target = $(this.options.target)
11-
.on('scroll.bs.affix.data-api', this.checkPosition.bind(this))
12-
.on('click.bs.affix.data-api', this.checkPositionWithEventLoop.bind(this));
13-
this.$element = $(element);
14-
this.affixed = null;
15-
this.unpin = null;
16-
this.pinnedOffset = null;
17-
this.checkPosition();
18-
}
19-
getState(scrollHeight, height, offsetTop, offsetBottom) {
20-
let scrollTop = this.$target.scrollTop();
21-
let position = this.$element.offset();
22-
let targetHeight = this.$target.height();
23-
if (offsetTop != null && this.affixed === 'top') return scrollTop < offsetTop ? 'top' : false;
24-
if (this.affixed === 'bottom') {
25-
if (offsetTop != null) return scrollTop + this.unpin <= position.top ? false : 'bottom';
26-
return scrollTop + targetHeight <= scrollHeight - offsetBottom ? false : 'bottom';
27-
}
28-
let initializing = this.affixed == null;
29-
let colliderTop = initializing ? scrollTop : position.top;
30-
let colliderHeight = initializing ? targetHeight : height;
31-
if (offsetTop != null && scrollTop <= offsetTop) return 'top';
32-
if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom';
33-
return false;
34-
}
35-
getPinnedOffset() {
36-
if (this.pinnedOffset) return this.pinnedOffset;
37-
this.$element.removeClass(Affix.RESET).addClass('affix');
38-
let scrollTop = this.$target.scrollTop();
39-
let position = this.$element.offset();
40-
return (this.pinnedOffset = position.top - scrollTop);
3+
var Affix = {
4+
init: function(element, options) {
5+
this.options = Object.assign({
6+
offset: 0,
7+
target: window
8+
}, options);
9+
this.target = this.options.target;
10+
this.target.addEventListener('scroll', this.checkPosition.bind(this));
11+
window.matchMedia('(min-width: 992px)').addListener(event => {
12+
if (event.matches) this.checkPosition();
13+
});
14+
this.element = element;
15+
this.affixed = null;
16+
this.unpin = null;
17+
this.pinnedOffset = null;
18+
this.checkPosition();
19+
},
20+
getState: function(scrollHeight, height, offsetTop, offsetBottom) {
21+
let scrollTop = this.target.scrollY;
22+
let targetHeight = this.target.innerHeight;
23+
if (offsetTop != null && this.affixed === 'top') return scrollTop < offsetTop ? 'top' : false;
24+
if (this.affixed === 'bottom') {
25+
if (offsetTop != null) return scrollTop + this.unpin <= $(this.element).offset().top ? false : 'bottom';
26+
return scrollTop + targetHeight <= scrollHeight - offsetBottom ? false : 'bottom';
4127
}
42-
checkPositionWithEventLoop() {
43-
setTimeout(this.checkPosition.bind(this), 1);
28+
let initializing = this.affixed === null;
29+
let colliderTop = initializing ? scrollTop : $(this.element).offset().top;
30+
let colliderHeight = initializing ? targetHeight : height;
31+
if (offsetTop != null && scrollTop <= offsetTop) return 'top';
32+
if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom';
33+
return false;
34+
},
35+
getPinnedOffset: function() {
36+
if (this.pinnedOffset) return this.pinnedOffset;
37+
this.element.classList.remove('affix-top', 'affix-bottom');
38+
this.element.classList.add('affix');
39+
let scrollTop = this.target.scrollY;
40+
return (this.pinnedOffset = $(this.element).offset().top - scrollTop);
41+
},
42+
checkPosition: function() {
43+
if (window.getComputedStyle(this.element).display === 'none') return;
44+
let height = $(this.element).height();
45+
let offset = this.options.offset;
46+
let offsetTop = offset.top;
47+
let offsetBottom = offset.bottom;
48+
let scrollHeight = document.body.scrollHeight;
49+
let affix = this.getState(scrollHeight, height, offsetTop, offsetBottom);
50+
if (this.affixed !== affix) {
51+
if (this.unpin != null) this.element.style.top = '';
52+
let affixType = 'affix' + (affix ? '-' + affix : '');
53+
this.affixed = affix;
54+
this.unpin = affix === 'bottom' ? this.getPinnedOffset() : null;
55+
this.element.classList.remove('affix', 'affix-top', 'affix-bottom');
56+
this.element.classList.add(affixType);
4457
}
45-
checkPosition() {
46-
if (!this.$element.is(':visible')) return;
47-
let height = this.$element.height();
48-
let offset = this.options.offset;
49-
let offsetTop = offset.top;
50-
let offsetBottom = offset.bottom;
51-
let scrollHeight = Math.max($(document).height(), $(document.body).height());
52-
if (typeof offset !== 'object') offsetBottom = offsetTop = offset;
53-
if (typeof offsetTop === 'function') offsetTop = offset.top(this.$element);
54-
if (typeof offsetBottom === 'function') offsetBottom = offset.bottom(this.$element);
55-
let affix = this.getState(scrollHeight, height, offsetTop, offsetBottom);
56-
if (this.affixed !== affix) {
57-
if (this.unpin != null) this.$element.css('top', '');
58-
let affixType = 'affix' + (affix ? '-' + affix : '');
59-
let e = new $.Event(affixType + '.bs.affix');
60-
this.$element.trigger(e);
61-
if (e.isDefaultPrevented()) return;
62-
this.affixed = affix;
63-
this.unpin = affix === 'bottom' ? this.getPinnedOffset() : null;
64-
this.$element
65-
.removeClass(Affix.RESET)
66-
.addClass(affixType)
67-
.trigger(affixType.replace('affix', 'affixed') + '.bs.affix');
68-
}
69-
if (affix === 'bottom') {
70-
this.$element.offset({
71-
top: scrollHeight - height - offsetBottom
72-
});
73-
}
58+
if (affix === 'bottom') {
59+
$(this.element).offset({
60+
top: scrollHeight - height - offsetBottom
61+
});
7462
}
7563
}
76-
77-
Affix.RESET = 'affix affix-top affix-bottom';
78-
79-
function Plugin(option) {
80-
return this.each(function() {
81-
let $this = $(this);
82-
let data = $this.data('bs.affix');
83-
let options = typeof option === 'object' && option;
84-
if (!data) $this.data('bs.affix', data = new Affix(this, options));
85-
if (typeof option === 'string') data[option]();
86-
});
87-
}
88-
89-
$.fn.affix = Plugin;
90-
$.fn.affix.Constructor = Affix;
91-
})(jQuery);
64+
};
9265

9366
window.addEventListener('DOMContentLoaded', () => {
9467
const sidebarOffset = CONFIG.sidebar.offset || 12;
95-
const sidebarInner = document.querySelector('.sidebar-inner');
96-
97-
const getHeaderOffset = () => document.querySelector('.header-inner').offsetHeight + sidebarOffset;
9868

99-
const getFooterOffset = () => {
100-
let footer = document.querySelector('#footer');
101-
let footerInner = document.querySelector('.footer-inner');
102-
let footerMargin = footer.offsetHeight - footerInner.offsetHeight;
103-
let footerOffset = footer.offsetHeight + footerMargin;
104-
return footerOffset;
105-
};
69+
let headerOffset = document.querySelector('.header-inner').offsetHeight + sidebarOffset;
70+
let footer = document.querySelector('#footer');
71+
let footerInner = document.querySelector('.footer-inner');
72+
let footerMargin = footer.offsetHeight - footerInner.offsetHeight;
73+
let footerOffset = footer.offsetHeight + footerMargin;
10674

107-
const initAffix = () => {
108-
let headerOffset = getHeaderOffset();
109-
let footerOffset = getFooterOffset();
110-
$('.sidebar-inner').affix({
111-
offset: {
112-
top : headerOffset - sidebarOffset,
113-
bottom: footerOffset
114-
}
115-
});
116-
document.querySelector('#sidebar').css({
117-
'margin-top' : `${headerOffset}px`,
118-
'margin-left': 'auto'
119-
});
120-
};
121-
122-
window.matchMedia('(min-width: 992px)').addListener(event => {
123-
if (event.matches) {
124-
$(window).off('.affix');
125-
$('.sidebar-inner').removeData('bs.affix');
126-
sidebarInner.classList.remove('affix', 'affix-top', 'affix-bottom');
127-
initAffix();
75+
Affix.init(document.querySelector('.sidebar-inner'), {
76+
offset: {
77+
top : headerOffset - sidebarOffset,
78+
bottom: footerOffset
12879
}
12980
});
130-
131-
initAffix();
81+
document.querySelector('.sidebar').css({
82+
'margin-top' : `${headerOffset}px`,
83+
'margin-left': 'auto'
84+
});
13285
});

0 commit comments

Comments
 (0)