UNPKG

7.13 kBJavaScriptView Raw
1'use strict';
2
3
4import $ from 'jquery';
5import { GetYoDigits } from './foundation.util.core';
6import { Plugin } from './foundation.plugin';
7import { SmoothScroll } from './foundation.smoothScroll';
8
9/**
10 * Magellan module.
11 * @module foundation.magellan
12 * @requires foundation.smoothScroll
13 */
14
15class Magellan extends Plugin {
16 /**
17 * Creates a new instance of Magellan.
18 * @class
19 * @name Magellan
20 * @fires Magellan#init
21 * @param {Object} element - jQuery object to add the trigger to.
22 * @param {Object} options - Overrides to the default plugin settings.
23 */
24 _setup(element, options) {
25 this.$element = element;
26 this.options = $.extend({}, Magellan.defaults, this.$element.data(), options);
27 this.className = 'Magellan'; // ie9 back compat
28
29 this._init();
30 this.calcPoints();
31 }
32
33 /**
34 * Initializes the Magellan plugin and calls functions to get equalizer functioning on load.
35 * @private
36 */
37 _init() {
38 var id = this.$element[0].id || GetYoDigits(6, 'magellan');
39 var _this = this;
40 this.$targets = $('[data-magellan-target]');
41 this.$links = this.$element.find('a');
42 this.$element.attr({
43 'data-resize': id,
44 'data-scroll': id,
45 'id': id
46 });
47 this.$active = $();
48 this.scrollPos = parseInt(window.pageYOffset, 10);
49
50 this._events();
51 }
52
53 /**
54 * Calculates an array of pixel values that are the demarcation lines between locations on the page.
55 * Can be invoked if new elements are added or the size of a location changes.
56 * @function
57 */
58 calcPoints() {
59 var _this = this,
60 body = document.body,
61 html = document.documentElement;
62
63 this.points = [];
64 this.winHeight = Math.round(Math.max(window.innerHeight, html.clientHeight));
65 this.docHeight = Math.round(Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight));
66
67 this.$targets.each(function(){
68 var $tar = $(this),
69 pt = Math.round($tar.offset().top - _this.options.threshold);
70 $tar.targetPoint = pt;
71 _this.points.push(pt);
72 });
73 }
74
75 /**
76 * Initializes events for Magellan.
77 * @private
78 */
79 _events() {
80 var _this = this,
81 $body = $('html, body'),
82 opts = {
83 duration: _this.options.animationDuration,
84 easing: _this.options.animationEasing
85 };
86 $(window).one('load', function(){
87 if(_this.options.deepLinking){
88 if(location.hash){
89 _this.scrollToLoc(location.hash);
90 }
91 }
92 _this.calcPoints();
93 _this._updateActive();
94 });
95
96 this.$element.on({
97 'resizeme.zf.trigger': this.reflow.bind(this),
98 'scrollme.zf.trigger': this._updateActive.bind(this)
99 }).on('click.zf.magellan', 'a[href^="#"]', function(e) {
100 e.preventDefault();
101 var arrival = this.getAttribute('href');
102 _this.scrollToLoc(arrival);
103 });
104
105 this._deepLinkScroll = function(e) {
106 if(_this.options.deepLinking) {
107 _this.scrollToLoc(window.location.hash);
108 }
109 };
110
111 $(window).on('popstate', this._deepLinkScroll);
112 }
113
114 /**
115 * Function to scroll to a given location on the page.
116 * @param {String} loc - a properly formatted jQuery id selector. Example: '#foo'
117 * @function
118 */
119 scrollToLoc(loc) {
120 this._inTransition = true;
121 var _this = this;
122
123 var options = {
124 animationEasing: this.options.animationEasing,
125 animationDuration: this.options.animationDuration,
126 threshold: this.options.threshold,
127 offset: this.options.offset
128 };
129
130 SmoothScroll.scrollToLoc(loc, options, function() {
131 _this._inTransition = false;
132 _this._updateActive();
133 })
134 }
135
136 /**
137 * Calls necessary functions to update Magellan upon DOM change
138 * @function
139 */
140 reflow() {
141 this.calcPoints();
142 this._updateActive();
143 }
144
145 /**
146 * Updates the visibility of an active location link, and updates the url hash for the page, if deepLinking enabled.
147 * @private
148 * @function
149 * @fires Magellan#update
150 */
151 _updateActive(/*evt, elem, scrollPos*/) {
152 if(this._inTransition) {return;}
153 var winPos = /*scrollPos ||*/ parseInt(window.pageYOffset, 10),
154 curIdx;
155
156 if(winPos + this.winHeight === this.docHeight){ curIdx = this.points.length - 1; }
157 else if(winPos < this.points[0]){ curIdx = undefined; }
158 else{
159 var isDown = this.scrollPos < winPos,
160 _this = this,
161 curVisible = this.points.filter(function(p, i){
162 return isDown ? p - _this.options.offset <= winPos : p - _this.options.offset - _this.options.threshold <= winPos;
163 });
164 curIdx = curVisible.length ? curVisible.length - 1 : 0;
165 }
166
167 this.$active.removeClass(this.options.activeClass);
168 this.$active = this.$links.filter('[href="#' + this.$targets.eq(curIdx).data('magellan-target') + '"]').addClass(this.options.activeClass);
169
170 if(this.options.deepLinking){
171 var hash = "";
172 if(curIdx != undefined){
173 hash = this.$active[0].getAttribute('href');
174 }
175 if(hash !== window.location.hash) {
176 if(window.history.pushState){
177 window.history.pushState(null, null, hash);
178 }else{
179 window.location.hash = hash;
180 }
181 }
182 }
183
184 this.scrollPos = winPos;
185 /**
186 * Fires when magellan is finished updating to the new active element.
187 * @event Magellan#update
188 */
189 this.$element.trigger('update.zf.magellan', [this.$active]);
190 }
191
192 /**
193 * Destroys an instance of Magellan and resets the url of the window.
194 * @function
195 */
196 _destroy() {
197 this.$element.off('.zf.trigger .zf.magellan')
198 .find(`.${this.options.activeClass}`).removeClass(this.options.activeClass);
199
200 if(this.options.deepLinking){
201 var hash = this.$active[0].getAttribute('href');
202 window.location.hash.replace(hash, '');
203 }
204 $(window).off('popstate', this._deepLinkScroll);
205 }
206}
207
208/**
209 * Default settings for plugin
210 */
211Magellan.defaults = {
212 /**
213 * Amount of time, in ms, the animated scrolling should take between locations.
214 * @option
215 * @type {number}
216 * @default 500
217 */
218 animationDuration: 500,
219 /**
220 * Animation style to use when scrolling between locations. Can be `'swing'` or `'linear'`.
221 * @option
222 * @type {string}
223 * @default 'linear'
224 * @see {@link https://api.jquery.com/animate|Jquery animate}
225 */
226 animationEasing: 'linear',
227 /**
228 * Number of pixels to use as a marker for location changes.
229 * @option
230 * @type {number}
231 * @default 50
232 */
233 threshold: 50,
234 /**
235 * Class applied to the active locations link on the magellan container.
236 * @option
237 * @type {string}
238 * @default 'is-active'
239 */
240 activeClass: 'is-active',
241 /**
242 * Allows the script to manipulate the url of the current page, and if supported, alter the history.
243 * @option
244 * @type {boolean}
245 * @default false
246 */
247 deepLinking: false,
248 /**
249 * Number of pixels to offset the scroll of the page on item click if using a sticky nav bar.
250 * @option
251 * @type {number}
252 * @default 0
253 */
254 offset: 0
255}
256
257export {Magellan};