-
Notifications
You must be signed in to change notification settings - Fork 34
/
jq-sticky-anything.js
214 lines (173 loc) · 10.2 KB
/
jq-sticky-anything.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
/**
* @preserve Sticky Anything 2.0.1 | @senff | GPL2 Licensed
*/
(function ($) {
$.fn.stickThis = function(options) {
var settings = $.extend({
// Default
top: 0,
minscreenwidth: 0,
maxscreenwidth: 99999,
zindex: 1,
debugmode: false,
pushup: ''
}, options );
var numElements = $(this).length;
var numPushElements = $(settings.pushup).length;
if (numPushElements < 1) {
// There are no elements on the page with the called selector for the Push-up Element.
if((settings.debugmode == true) && (settings.pushup)) {
console.error('STICKY ANYTHING DEBUG: There are no elements with the selector/class/ID you selected for the Push-up element ("'+settings.pushup+'").');
}
// Resetting it to NOTHING.
settings.pushup = '';
} else if (numPushElements > 1) {
// You can't use more than one element to push up the sticky element.
// Make sure that you use a selector that applies to only ONE SINGLE element on the page.
// Want to find out quickly where all the elements are that you targeted? Uncomment the next line to debug.
// $(settings.pushup).css('border','solid 3px #ff0000');
if(settings.debugmode == true) {
console.error('STICKY ANYTHING DEBUG: There are '+numPushElements+' elements on the page with the selector/class/ID you selected for the push-up element ("'+settings.pushup+'"). You can select only ONE element to push the sticky element up.');
}
// Resetting it to NOTHING.
settings.pushup = '';
}
if (numElements < 1) {
// There are no elements on the page with the called selector.
if(settings.debugmode == true) {
console.error('STICKY ANYTHING DEBUG: There are no elements with the selector/class/ID you selected for the sticky element ("'+this.selector+'").');
}
} else if (numElements > 1) {
// This is not going to work either. You can't make more than one element sticky. They will only get in eachother's way.
// Make sure that you use a selector that applies to only ONE SINGLE element on the page.
// Want to find out quickly where all the elements are that you targeted? Uncomment the next line to debug.
// $(this).css('border','solid 3px #00ff00');
if(settings.debugmode == true) {
console.error('STICKY ANYTHING DEBUG: There There are '+numPushElements+' elements with the selector/class/ID you selected for the sticky element ("'+this.selector+'"). You can only make ONE element sticky.');
}
} else {
// CALLING THE MAIN FUNCTION
$(this).addClass('sticky-element-original').addClass('sticky-element-not-sticky');
orgAssignedStyles = cssStyles($(this)); // All original element styles, assigned by CSS.
orgInlineStyles = $('.sticky-element-original').attr('style');
if (orgInlineStyles == null) {
orgInlineStyles = '';
}
createPlaceholder();
checkElement = setInterval(function(){stickIt(settings.top,settings.minscreenwidth,settings.maxscreenwidth,settings.zindex,settings.pushup,orgAssignedStyles,orgInlineStyles)},10);
}
return this;
};
function stickIt(stickyTop,minwidth,maxwidth,stickyZindex,pushup,originalAssignedStyles,originalInlineStyles) {
// We need to check the position of the ACTIVE element to determine if we've scrolled enough.
// This is the original one when it's not sticky, but when it's sticky, it's the placeholder.
$listenerElement = $('.sticky-element-active');
var orgElementPos = $listenerElement.offset();
orgElementTop = orgElementPos.top;
if(pushup) {
var pushElementPos = $(pushup).offset();
pushElementTop = pushElementPos.top;
}
// Calculating actual viewport width
var e = window, a = 'inner';
if (!('innerWidth' in window )) {
a = 'client';
e = document.documentElement || document.body;
}
viewport = e[ a+'Width' ];
if (($(window).scrollTop() >= (orgElementTop - stickyTop)) && (viewport >= minwidth) && (viewport <= maxwidth)) {
// We've scrolled PAST the original position; this is where we need to make the element sticky.
// Placeholder element should always have same left position as original element (see comment below).
// The sticky element will NOT have a TOP or the LEFT margin. This is because the left/top reference point of the original
// element does not consider the margin. So, we're checking the left/top point of the actual original element and then
// use that position for the sticky element.
// LEFT POSITION
coordsOrgElement = $listenerElement.offset();
leftOrgElement = coordsOrgElement.left; // This is the position REGARDLESS of the margin.
// WIDTH/HEIGHT
// The placeholder needs to have the width and height of the original element, WITHOUT the margins but WITH the padding and borders
// Whatever margins the original has, the placeholder needs to have that too.
widthPlaceholder = $listenerElement[0].getBoundingClientRect().width;
if (!widthPlaceholder) {
widthPlaceholder = $listenerElement.css('width'); // FALLBACK for subpixels
}
heightPlaceholder = $listenerElement[0].getBoundingClientRect().height;
if (!heightPlaceholder) {
heightPlaceholder = $listenerElement.css('height'); // FALLBACK for subpixels
}
// WIDTH/HEIGHT OF STICKY ELEMENT
// The original element though, needs to have the inner width and height of the original (non-sticky) element
// No padding, no borders, because that will be applied later anyway, regardless of box-sizing
widthSticky = $('.sticky-element-original').css('width');
if(widthSticky == '0px') {
widthSticky = ($('.sticky-element-original')[0].getBoundingClientRect().width);
}
heightSticky = $('.sticky-element-original').height();
// PADDING
// If padding is percentages, convert to pixels when it becomes sticky
// Just a leftover from the old method. We will not use padding for the placeholder
paddingOrgElement = [$('.sticky-element-original').css('padding-top'), $('.sticky-element-original').css('padding-right'), $('.sticky-element-original').css('padding-bottom'),$('.sticky-element-original').css('padding-left')];
paddingSticky = paddingOrgElement[0] + ' ' + paddingOrgElement[1] + ' ' + paddingOrgElement[2] + ' ' + paddingOrgElement[3];
// MARGIN
marginOrgElement = [$listenerElement.css('margin-top'), $listenerElement.css('margin-right'), $listenerElement.css('margin-bottom'),$listenerElement.css('margin-left')];
marginPlaceholder = marginOrgElement[0] + ' ' + marginOrgElement[1] + ' ' + marginOrgElement[2] + ' ' + marginOrgElement[3];
// OTHER ELEMENTS
// if original has float, display, etc., we need to assign that to the placeholder
// Though not as important as the width/height/margin/padding
assignedStyles = '';
for (var importantStyle in originalAssignedStyles) {
if (originalAssignedStyles[importantStyle] == 'inline') {
assignedStyles += importantStyle+':inline-block; ';
} else {
assignedStyles += importantStyle+':'+originalAssignedStyles[importantStyle]+'; ';
}
}
// Fixes bug where height of original element returns zero
// Is this still needed for the post-2.0 mode??
elementHeight = 0;
if (heightPlaceholder < 1) {
elementHeight = $('.sticky-element-cloned').outerHeight();
} else {
elementHeight = $('.sticky-element-original').outerHeight();
}
// If scrolled position = pushup-element (top coordinate) - space between top and element - element height
// In other words, if the pushup element hits the bottom of the sticky element
if (pushup && ($(window).scrollTop() > (pushElementTop-stickyTop-elementHeight))) {
stickyTopMargin = (pushElementTop-stickyTop-elementHeight-$(window).scrollTop());
} else {
stickyTopMargin = 0;
}
assignedStyles += 'width:'+widthPlaceholder+'px; height:'+heightPlaceholder+'px; margin:'+marginPlaceholder+';';
$('.sticky-element-original').removeClass('sticky-element-not-sticky').addClass('sticky-element-sticky').removeClass('sticky-element-active').css('position','fixed').css('left',leftOrgElement+'px').css('top',stickyTop+'px').css('width',widthSticky).css('margin-left',0).css('padding',paddingSticky).css('margin-top',stickyTopMargin).css('z-index',stickyZindex);
if(!$('.sticky-element-placeholder').hasClass('sticky-element-active')) {
$('.sticky-element-placeholder').addClass('sticky-element-active').attr('style',assignedStyles);
}
} else {
// not scrolled past the menu; only show the original element.
$('.sticky-element-original').addClass('sticky-element-not-sticky').removeClass('sticky-element-sticky').addClass('sticky-element-active').attr('style',originalInlineStyles);
if($('.sticky-element-placeholder').hasClass('sticky-element-active')) {
$('.sticky-element-placeholder').removeClass('sticky-element-active').removeAttr('style').css('width','0').css('height','0').css('margin','0').css('padding','0');
}
}
}
function createPlaceholder() {
$('.sticky-element-original').addClass('sticky-element-active').before('<div class="sticky-element-placeholder" style="width:0; height:0; margin:0; padding:0; visibility:hidden;"></div>');
}
// Helper function: get the important CSS rules from an element
function cssStyles(el) {
o = {};
o['display'] = el.css('display');
o['float'] = el.css('float');
o['flex'] = el.css('flex');
o['box-sizing'] = el.css('box-sizing');
o['clear'] = el.css('clear');
o['overflow'] = el.css('overflow');
o['transform'] = el.css('transform');
// For some reason, this original loop doesn't work with some themes/plugins.
// importantStyles = ['display','float','flex','box-sizing','clear','overflow','transform'];
// for(var styleProp in importantStyles) {
// o[importantStyles[styleProp]] = el.css(importantStyles[styleProp]);
// }
return o;
}
}(jQuery));