2gis-maps
Version:
Interactive 2GIS maps API, based on Leaflet
213 lines (180 loc) • 8.66 kB
JavaScript
/* Fixable elements plugin for baron 0.6+ */
(function(window, undefined) {
var fix = function(userParams) {
var elements, viewPortSize,
params = { // Default params
outside: '',
before: '',
after: '',
past: '',
future: '',
radius: 0,
minView: 0
},
topFixHeights = [], // inline style for element
topRealHeights = [], // real offset position when not fixed
headerTops = [],
scroller = this.scroller,
eventManager = this.event,
$ = this.$,
self = this;
function fixElement(i, pos) {
if (viewPortSize < (params.minView || 0)) { // No headers fixing when no enought space for viewport
pos = undefined;
}
if (pos !== undefined) {
pos += 'px';
this.$(elements[i]).css(this.origin.pos, pos).addClass(params.outside);
} else {
this.$(elements[i]).css(this.origin.pos, '').removeClass(params.outside);
}
}
function bubbleWheel(e) {
try {
i = document.createEvent('WheelEvent'); // i - for extra byte
// evt.initWebKitWheelEvent(deltaX, deltaY, window, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey);
i.initWebKitWheelEvent(e.originalEvent.wheelDeltaX, e.originalEvent.wheelDeltaY);
scroller.dispatchEvent(i);
e.preventDefault();
} catch (e) {}
}
function init(_params) {
var pos;
for (var key in _params) {
params[key] = _params[key];
}
elements = this.$(params.elements, this.scroller);
if (elements) {
viewPortSize = this.scroller[this.origin.client];
for (var i = 0 ; i < elements.length ; i++) {
// Variable header heights
pos = {};
pos[this.origin.size] = elements[i][this.origin.offset];
if (elements[i].parentNode !== this.scroller) {
this.$(elements[i].parentNode).css(pos);
}
pos = {};
pos[this.origin.crossSize] = elements[i].parentNode[this.origin.crossClient];
this.$(elements[i]).css(pos);
// Between fixed headers
viewPortSize -= elements[i][this.origin.offset];
headerTops[i] = elements[i].parentNode[this.origin.offsetPos]; // No paddings for parentNode
// Summary elements height above current
topFixHeights[i] = (topFixHeights[i - 1] || 0); // Not zero because of negative margins
topRealHeights[i] = (topRealHeights[i - 1] || Math.min(headerTops[i], 0));
if (elements[i - 1]) {
topFixHeights[i] += elements[i - 1][this.origin.offset];
topRealHeights[i] += elements[i - 1][this.origin.offset];
}
if ( !(i == 0 && headerTops[i] == 0)/* && force */) {
this.event(elements[i], 'mousewheel', bubbleWheel, 'off');
this.event(elements[i], 'mousewheel', bubbleWheel);
}
}
if (params.limiter && elements[0]) { // Bottom edge of first header as top limit for track
if (this.track && this.track != this.scroller) {
pos = {};
pos[this.origin.pos] = elements[0].parentNode[this.origin.offset];
this.$(this.track).css(pos);
} else {
this.barTopLimit = elements[0].parentNode[this.origin.offset];
}
// this.barTopLimit = elements[0].parentNode[this.origin.offset];
this.scroll();
}
if (params.limiter === false) { // undefined (in second fix instance) should have no influence on bar limit
this.barTopLimit = 0;
}
}
var event = {
element: elements,
handler: function() {
var parent = $(this)[0].parentNode,
top = parent.offsetTop,
num;
// finding num -> elements[num] === this
for (var i = 0 ; i < elements.length ; i++ ) {
if (elements[i] === this) num = i;
}
var pos = top - topFixHeights[num];
if (params.scroll) { // User defined callback
params.scroll({
x1: self.scroller.scrollTop,
x2: pos
});
} else {
self.scroller.scrollTop = pos;
}
},
type: 'click'
};
if (params.clickable) {
this._eventHandlers.push(event); // For auto-dispose
eventManager(event.element, event.type, event.handler, 'off');
eventManager(event.element, event.type, event.handler, 'on');
}
}
this.on('init', init, userParams);
this.on('init scroll', function() {
var fixState, hTop,
fixFlag = []; // 1 - past, 2 - future, 3 - current (not fixed)
if (elements) {
var change;
// fixFlag update
for (var i = 0 ; i < elements.length ; i++) {
fixState = 0;
if (headerTops[i] - this.pos() < topRealHeights[i] + params.radius) {
// Header trying to go up
fixState = 1;
hTop = topFixHeights[i];
} else if (headerTops[i] - this.pos() > topRealHeights[i] + viewPortSize - params.radius) {
// Header trying to go down
fixState = 2;
hTop = topFixHeights[i] + viewPortSize;
} else {
// Header in viewport
fixState = 3;
hTop = undefined;
}
if (fixState != fixFlag[i]) {
fixElement.call(this, i, hTop);
fixFlag[i] = fixState;
change = true;
}
}
// Adding positioning classes (on last top and first bottom header)
if (change) { // At leats one change in elements flag structure occured
for (i = 0 ; i < elements.length ; i++) {
if (fixFlag[i] == 1 && params.past) {
this.$(elements[i]).addClass(params.past).removeClass(params.future);
}
if (fixFlag[i] == 2 && params.future) {
this.$(elements[i]).addClass(params.future).removeClass(params.past);
}
if (fixFlag[i] == 3 && (params.future || params.past)) {
this.$(elements[i]).removeClass(params.past).removeClass(params.future);
}
if (fixFlag[i] != fixFlag[i + 1] && fixFlag[i] == 1 && params.before) {
this.$(elements[i]).addClass(params.before).removeClass(params.after); // Last top fixed header
} else if (fixFlag[i] != fixFlag[i - 1] && fixFlag[i] == 2 && params.after) {
this.$(elements[i]).addClass(params.after).removeClass(params.before); // First bottom fixed header
} else {
this.$(elements[i]).removeClass(params.before).removeClass(params.after);
}
}
}
}
});
this.on('resize upd', function(updParams) {
init.call(this, updParams && updParams.fix);
});
};
baron.fn.fix = function(params) {
var i = 0;
while (this[i]) {
fix.call(this[i], params);
i++;
}
return this;
};
})(window);