Last active
August 12, 2019 10:41
-
-
Save james-jlo-long/fe69c667463d127063617a2b4d5a54c5 to your computer and use it in GitHub Desktop.
Listening for break point changes without using the window resize event.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @file A class that listens for breakpoints. This is far more efficient | |
* and accurate than relying on the window resize event. | |
* @license MIT | |
* @author James "JLo" Long <[email protected]> | |
* @requires Observer | |
*/ | |
var BreakPoints = (function () { | |
"use strict"; | |
var EVENT_NAME_GLUE = "-"; | |
/** | |
* @class BreakPoints | |
* @param {Object} [points] | |
* Optional initial break points. See | |
* {@link BreakPoints#addBreakPoint} for details. | |
*/ | |
var Points = function (points) { | |
/** | |
* A collection of break point names and matchMedia instances. | |
* @type {Object} | |
*/ | |
this.media = {}; | |
if (points) { | |
Object.keys(points).forEach(function (name) { | |
this.addBreakPoint(name, points[name]); | |
}, this); | |
} | |
}; | |
Points.prototype = Observer.extend(/** @lends BreakPoints.prototype */{ | |
/** | |
* Adds a break point. | |
* | |
* @param {String} name | |
* The name of the break point. | |
* @param {Number|String} mediaQuery | |
* Either the minimum number of pixels (e.g. 100 is converted | |
* into "(min-width: 100px)") or a string with a media query | |
* (e.g. "(max-width: 30em)"). | |
*/ | |
addBreakPoint: function (name, mediaQuery) { | |
var that = this; | |
var mm = window.matchMedia( | |
typeof mediaQuery === "string" | |
? mediaQuery | |
: "(min-width:" + mediaQuery + "px)" | |
); | |
mm.addListener(function (media) { | |
var eventName = ( | |
media.matches | |
? "enter" | |
: "leave" | |
); | |
/** | |
* The generic change event that fires whenever a break point | |
* change occurs. | |
* @event BreakPoints#change | |
*/ | |
that.dispatchEvent("change", media); | |
/** | |
* The change event that fires whenever a specific break point | |
* changes. | |
* @event BreakPoints#change-(name) | |
*/ | |
that.dispatchEvent("change" + EVENT_NAME_GLUE + name, media); | |
/** | |
* The event that fires when a break point becomes active. | |
* @event BreakPoints#enter | |
*/ | |
/** | |
* The event that fires when a break point becomes inactive. | |
* @event BreakPoints#leave | |
*/ | |
that.dispatchEvent(eventName, media); | |
/** | |
* The event that fires when a specific break point becomes | |
* active. | |
* @event BreakPoints#enter-(name) | |
*/ | |
/** | |
* The event that fires when a specific break point becomes | |
* inactive. | |
* @event BreakPoints#leave-(name) | |
*/ | |
that.dispatchEvent( | |
eventName + EVENT_NAME_GLUE + name, | |
media | |
); | |
}); | |
that.media[name] = mm; | |
}, | |
/** | |
* Adds an event listener to a breakpoint change. | |
* | |
* @param {String} event | |
* Name of the event to listen for. | |
* @param {String} [name] | |
* Optional name of the break point whose event should be | |
* listened for. | |
* @param {Function} handler | |
* Function to execute when the event occurs. | |
* @param {Boolean} [autoExecute=false] | |
* If true and if name is given, the handler will execute | |
* instantly if it would have matched. | |
*/ | |
on: function (event, name, handler, autoExecute) { | |
var isNameGiven = typeof name === "string"; | |
var func = ( | |
isNameGiven | |
? handler | |
: name | |
); | |
var eventName = ( | |
isNameGiven | |
? (event + EVENT_NAME_GLUE + name) | |
: event | |
); | |
var matches = false; | |
this.addEventListener(eventName, func); | |
if (isNameGiven && autoExecute) { | |
matches = this.matches(name); | |
if ( | |
event === "change" | |
|| (event === "enter" && matches) | |
|| (event === "leave" && !matches) | |
) { | |
func.call( | |
this.getEventElement(), | |
this.createEvent(eventName, this.media[name]) | |
); | |
} | |
} | |
}, | |
/** | |
* Removes an event listener from a breakpoint change. | |
* | |
* @param {String} event | |
* Name of the event to stop listening for. | |
* @param {String} [name] | |
* Optional name of the break point whose event should no longer | |
* be listened for. | |
* @param {Function} handler | |
* Function to remove from the event. | |
*/ | |
off: function (event, name, handler) { | |
var isNameGiven = typeof name === "string"; | |
var func = ( | |
isNameGiven | |
? handler | |
: name | |
); | |
var eventName = ( | |
isNameGiven | |
? (event + EVENT_NAME_GLUE + name) | |
: event | |
); | |
this.removeEventListener(eventName, func); | |
}, | |
/** | |
* Checks to see if the given break point name matches. This function | |
* will return false if the break point name isn't recognised. | |
* | |
* @param {String} name | |
* Name of the break point to check. | |
* @return {Boolean} | |
* True if the break point matches false if it doesn't (or if | |
* the break point name isn't recognised). | |
*/ | |
matches: function (name) { | |
var media = this.media[name]; | |
return Boolean(media) && media.matches; | |
} | |
}); | |
return Points; | |
}()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! MIT lisence, author: James "JLo" Long <[email protected]> */ | |
var Observer=function(){"use strict";var t=new WeakMap,e=function(){};return e.prototype={getEventElement:function(){var e=t.get(this);return e||(e=document.createElement("div"),t.set(this,e)),e},addEventListener:function(t,e){$(this.getEventElement()).on(t,e)},removeEventListener:function(t,e){$(this.getEventElement()).off(t,e)},createEvent:function(t,e){return $.Event(t,{detail:e})},dispatchEvent:function(t,e){$(this.getEventElement()).trigger("string"==typeof t?this.createEvent(t,e):t)}},e.extend=function(t){return $.extend(Object.create(e.prototype),t)},e}(),BreakPoints=function(){"use strict";var t=function(t){this.media={},t&&Object.keys(t).forEach(function(e){this.addBreakPoint(e,t[e])},this)};return t.prototype=Observer.extend({addBreakPoint:function(t,e){var n=this,i=window.matchMedia("string"==typeof e?e:"(min-width:"+e+"px)");i.addListener(function(e){var i=e.matches?"enter":"leave";n.dispatchEvent("change",e),n.dispatchEvent("change-"+t,e),n.dispatchEvent(i,e),n.dispatchEvent(i+"-"+t,e)}),n.media[t]=i},on:function(t,e,n,i){var r="string"==typeof e,a=r?n:e,s=r?t+"-"+e:t,c=!1;this.addEventListener(s,a),r&&i&&(c=this.matches(e),("change"===t||"enter"===t&&c||"leave"===t&&!c)&&a.call(this.getEventElement(),this.createEvent(s,this.media[e])))},off:function(t,e,n){var i="string"==typeof e,r=i?n:e,a=i?t+"-"+e:t;this.removeEventListener(a,r)},matches:function(t){var e=this.media[t];return Boolean(e)&&e.matches}}),t}(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @file A basic jQuery-based observer. | |
* @license MIT | |
* @author James "JLo" Long <[email protected]> | |
* @requires jQuery | |
*/ | |
var Observer = (function () { | |
"use strict"; | |
var dummies = new WeakMap(); | |
/** | |
* @class Observer | |
*/ | |
var Obs = function () { | |
}; | |
Obs.prototype = /** @lends Observer.prototype */{ | |
/** | |
* Gets the element on which events will be heard and dispatched. By | |
* default, this function will create a unique div element but this | |
* method can be overridden to work on elements in the existing DOM | |
* tree, allowing observable events to be delegated. | |
* | |
* @return {Element} | |
* Event element. | |
*/ | |
getEventElement: function () { | |
var dummy = dummies.get(this); | |
if (!dummy) { | |
dummy = document.createElement("div"); | |
dummies.set(this, dummy); | |
} | |
return dummy; | |
}, | |
/** | |
* Adds an event listener to the event element (see | |
* {@link Observer#getEventElement}). Events always bubble. | |
* | |
* @param {String} eventName | |
* Name of the event to listen for. | |
* @param {Function} handler | |
* Function to execute on the event. | |
*/ | |
addEventListener: function (eventName, handler) { | |
$(this.getEventElement()).on(eventName, handler); | |
}, | |
/** | |
* Removes an event listener from the event element (see | |
* {@link Observer#getEventElement}). * @return {[type]} [description] | |
* | |
* @param {String} eventName | |
* Name of the event to stop listening for. | |
* @param {Function} handler | |
* Function to remove from the event. | |
*/ | |
removeEventListener: function (eventName, handler) { | |
$(this.getEventElement()).off(eventName, handler); | |
}, | |
/** | |
* Creates an event. | |
* | |
* @param {String} eventName | |
* Name of the event to create. | |
* @param {?} [detail] | |
* Optional detail for the event. | |
* @return {jQuery.Event} | |
* jQuery event. | |
*/ | |
createEvent: function (eventName, detail) { | |
return $.Event(eventName, { | |
detail: detail | |
}); | |
}, | |
/** | |
* Dispatches an event on the event element (see | |
* {@link Observer#getEventElement}). | |
* | |
* @param {String|jQuery.Event} event | |
* Either the name of the event or the event itself. | |
* @param {?} [detail] | |
* Optional detail for the event. This is ignored if the event | |
* parameter is already a jQuery.Event instance. | |
*/ | |
dispatchEvent: function (event, detail) { | |
$(this.getEventElement()).trigger( | |
typeof event === "string" | |
? this.createEvent(event, detail) | |
: event | |
); | |
} | |
}; | |
/** | |
* Helper function for creating a prototype object that includes the | |
* {@link Observer} methods. | |
* | |
* @memberof Observer | |
* @param {Object} prototype | |
* Methods to add to the new prototype. | |
* @return {Object} | |
* Prototype with a live link to the prototype of | |
* {@link Observer}. | |
*/ | |
Obs.extend = function (prototype) { | |
return $.extend(Object.create(Obs.prototype), prototype); | |
}; | |
return Obs; | |
}()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Listen for website break point changes using JavaScript but without relying | |
// on the window resize event. Not only does that fire a lot, but checking the | |
// window width isn't as accurate as you'd think. | |
// Creating an instance (based on Bootstrap 4) | |
var breakpoints = new BreakPoints({ | |
xs: 0, | |
sm: 576, | |
md: 768, | |
lg: 992, | |
xl: 1200 | |
}); | |
// You could also use a media query string either in this method or in the | |
// initial object. | |
breakpoints.addBreakPoint("custom", "(max-width: 30em)"); | |
// Does the break point match? | |
breakpoints.matches("sm"); // -> true if the screen is at least 576 pixels wide. | |
// Listening for a change. | |
// | |
// There are 3 events: | |
// - "change" (break point has either been entered or left) | |
// - "enter" (break point has been entered) | |
// - "leave" (break point has been left) | |
// Listen for all break point enters: | |
breakpoints.on("enter", function (e) { | |
// e.detail is a window.matchMedia instance. | |
}); | |
// Listen for the "md" break point leaving. | |
breakpoints.on("leave", "md", function (e) { | |
// e.detail is a window.matchMedia instance. | |
}); | |
// Automatically execute the function if it would have matched. | |
breakpoints.on("change", "lg", function (e) { | |
// ... | |
}, true); |
Note to self: the media
passed to the event should have a name
property so users can easily identify them from the generic change
event.
Probably worth wrapping it:
class BreakPointQuery {
constructor(/* String */ name, /* MediaQueryList */ matchMedia) {
this.mediaQueryList = matchMedia;
Object.defineProperties(this, {
name: {
configurable: false,
enumerable: true,
writable: false,
value: name
},
matches: {
get: function () {
return matchMedia.matches;
}
},
media: {
get: function () {
return matchMedia.media;
}
}
});
}
addListener(/* Function */ func) {
this.mediaQueryList.addListener(func);
}
removeListener(/* Function */ func) {
this.mediaQueryList.removeListener(func);
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add a flag to the end of
on
which executes the given handler if it would have.