lib/dd.breakpoints.js (260 lines of code) (raw):

/** * Breakpoints for JavaScript. Works with the Deloitte Digital SCSS @bp mixin and Less .bp mixin * * @namespace bp * @memberof DD * @version 2.0.4 * @copyright 2012-2021 Deloitte Digital Australia - http://www.deloittedigital.com/au * @author Deloitte Digital Australia deloittedigital@deloitte.com.au * @license BSD 3-Clause (http://opensource.org/licenses/BSD-3-Clause) */ (function (root, factory) { // UMD wrapper - Works with node, AMD and browser globals. // Using the returnExports pattern as a guide: // https://github.com/umdjs/umd/blob/master/templates/returnExports.js if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define([], factory); } else if (typeof module === 'object' && module.exports) { // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. module.exports = factory(); } else { // Browser globals (root is window) root.DD = root.DD || {}; root.DD.bp = factory(); } }(this, function () { var _minBreakpoints, _maxBreakpoints, _options = { isResponsive: true, baseFontSize: 16, breakpoints: [ { name: 'xxs', px: 359 }, { name: 'xs', px: 480 }, { name: 's', px: 640 }, { name: 'm', px: 768 }, { name: 'l', px: 1024 }, { name: 'xl', px: 1244 }, { name: 'xxl', px: 1410 }, { name: 'xxxl', px: 1570 }, { name: 'fhd', px: 1900 } ], staticRange: { min: 0, max: 'xl' } }, _initBreakpoints, _parseMinMaxInputs, _pxToEms, _bpToEms, _bpIsValidForStatic, _bpMin, _bpMax, _bpBetween, get, getHeight, is, isHeight, options; /** * Sorts the breakpoints and assigns them to an associative array for more efficient lookup. * Immediately invoked on initialisation * * @memberof DD.bp * @private */ _initBreakpoints = function() { //sort the breakpoints into order of smallest to largest var sortedBreakpoints = _options.breakpoints.sort(function(a, b) { // only sort if the correct objects are present if (a.px < b.px) { return -1; } if (a.px > b.px) { return 1; } return 0; }); // reset the breakpoints _minBreakpoints = {}; _maxBreakpoints = {}; // loop through sorted breakpoints to generate a quick lookup object using the name as a key for (var i = 0, len = sortedBreakpoints.length, last = len - 1; i < len; i += 1) { _minBreakpoints[sortedBreakpoints[i].name] = parseInt(sortedBreakpoints[i].px, 10); // skip the last item in the list as we assume there is no maximum for the last if (i < last) { // the max breakpoint of the current size is the next breakpoints // width minus 1px so there is no overlap between breakpoints _maxBreakpoints[sortedBreakpoints[i].name] = parseInt(sortedBreakpoints[i + 1].px - 1, 10); } } }; _initBreakpoints(); /** * Splits string syntax 'xs,m' into separate values 'xs' and 'm' * Converts string '5' to numeric 5 * * @memberof DD.bp * @private * @param {String|Number} min Number in pixels or string notation * @param {String|Number} max Number in pixels or string notation * @return {Object} Object containing the min and max values parsed as a number */ _parseMinMaxInputs = function(min, max) { var parseValue = function(val) { if (typeof (val) === 'string') { // Strip whitespace val = val.replace(/\s/g, ''); // If val only contains digits, convert it to a number if (/^\d+$/.test(val)) { val = parseInt(val, 10); } } return val; }, bpArray, resultMin = min, resultMax = max || 0; // check if it's using the string syntax, if so - split it if (typeof (min) === 'string' && min.indexOf(',') !== -1 && resultMax === 0) { bpArray = min.split(','); if (bpArray.length === 2) { resultMin = bpArray[0]; resultMax = bpArray[1]; } } return { min: parseValue(resultMin), max: parseValue(resultMax) }; }; /** * Converts a number of pixels into em * * @memberof DD.bp * @private * @param {Number} px Number in pixels * @return {String} The converted number in em as a string */ _pxToEms = function(px) { return px / _options.baseFontSize; }; /** * Converts a breakpoint name/value (e.g. l) to the px variable then to ems * * @memberof DD.bp * @private * @param {String|Number} breakpoint Breakpoint name as a string, or as a number in pixels * @param {Boolean} [isMax=false] Flag to determine if the min or max of the breakpoint needs to be used * @return {String} The converted number in em as a string */ _bpToEms = function(breakpoint, isMax) { if (typeof (breakpoint) === 'number') { return _pxToEms(breakpoint); } var list = (isMax === true) ? _maxBreakpoints : _minBreakpoints, ems = '0'; for (var key in list) { if (list.hasOwnProperty(key)) { if (breakpoint === key.toLowerCase()) { ems = _pxToEms(list[key]); } } } if (ems === '0') { console.warn('DD.bp: Breakpoint \'' + breakpoint + '\' doesn\'t exist - replacing with 0'); } return ems; }; /** * Checks if the breakpoint provided falls inside the valid static min/max region * * @memberof DD.bp * @private * @param {String|Number} min Breakpoint name as a string, as a number in pixels, or as string notation containing both breakpoints * @param {String|Number} max Breakpoint name as a string, or as a number in pixels * @param {Boolean} [property='width'] which property to check for (e.g. width or height) * @return {Boolean} If the breakpoint fits inside the static range or not */ _bpIsValidForStatic = function(min, max, property) { if (typeof (property) !== 'string') { property = 'width'; //default to width based media query } if (property !== 'width') { return false; } var bpValidMin = _bpToEms(_options.staticRange.min), bpValidMax = _bpToEms(_options.staticRange.max, true), parsed = _parseMinMaxInputs(min, max), bpMin = _bpToEms(parsed.min), bpMax = _bpToEms(parsed.max); // if max is 0 we have a min-and-above situation if (parsed.max === 0) { // need to check that the min is greater than the valid min, // AND also that the min is less than the valid maximum if (bpMin >= bpValidMin && bpMin < bpValidMax) { return true; } return false; } // if min is 0 we have a max-and-below situation if (parsed.min === 0) { if (bpMax >= bpValidMax) { return true; } return false; } // if the min is above the valid max, or the max is below the valid min if (bpMin > bpValidMax || bpMax < bpValidMin) { return false; } // if the breakpoint is a bp-between (assumed because $max and $min aren't 0) // don't show if the max isn't above the valid max if (bpMax < bpValidMax) { return false; } return true; }; /** * Returns a min-width media query based on bp name or px * * @memberof DD.bp * @private * @param {String|Number} min Breakpoint name as a string, or as a number in pixels * @param {String} [property='width'] Property to check using a media query. e.g. width or height * @return {String} Media query string */ _bpMin = function(min, property) { var bpMin = _bpToEms(min), bpType = (typeof (property) === 'string') ? property : 'width'; return '(min-' + bpType + ': ' + bpMin + 'em)'; }; /** * Returns a max-width media query based on bp name or px * * @memberof DD.bp * @private * @param {String|Number} max Breakpoint name as a string, or as a number in pixels * @param {String} [property='width'] Property to check using a media query. e.g. width or height * @return {String} Media query string */ _bpMax = function(max, property) { var bpMax = _bpToEms(max, true), bpType = (typeof (property) === 'string') ? property : 'width'; return '(max-' + bpType + ': ' + bpMax + 'em)'; }; /** * Returns a min-width and max-width media query based on bp name (can be the same bp name) or px * * @memberof DD.bp * @private * @param {String|Number} min Breakpoint name as a string, or as a number in pixels * @param {String|Number} max Breakpoint name as a string, or as a number in pixels * @param {String} [property='width'] Property to check using a media query. e.g. width or height * @return {String} Media query string */ _bpBetween = function(min, max, property) { var bpMin = _bpToEms(min), bpMax = _bpToEms(max, true), bpType = (typeof (property) === 'string') ? property : 'width'; return '(min-' + bpType + ': ' + bpMin + 'em) and (max-' + bpType + ': ' + bpMax + 'em)'; }; /** * Breakpoint function that can take the input of a min and max * breakpoint by name or number (in px) along with a property * (like width or height) and returns the media query as a string * * @memberof DD.bp * @example * // large and above * DD.bp.get('l'); * * @example * // 300px and above * DD.bp.get(300); * * @example * // large and below * DD.bp.get(0, 'l'); * * @example * // 300px and below * DD.bp.get(0, 300); * * @example * // Between small and large * DD.bp.get('s', 'l'); * * @example * // Between 100px and 300px * DD.bp.get(100, 300); * * @example * // High resolution displays (can use 'hdpi' as well) * DD.bp.get('retina'); * * @example * // Can mix and match names and numbers - between 200px and xlarge * DD.bp.get(200, 'xl'); * * @example * // Between small and 960px * DD.bp.get('s', 960); * * @example * // Can use a single string (no spaces) - useful for passing through from HTML to JS * DD.bp.get('m,l'); * * @example * // Can also mix names and numbers * DD.bp.get('xs,1000'); * * @param {String|Number} min Breakpoint name as a string, or as a number in pixels, or in comma separated string notation * @param {String|Number} [max=0] Breakpoint name as a string, or as a number in pixels * @param {String} [property='width'] Property to check using a media query. e.g. width or height * @return {String} Media query string */ get = function(min, max, property) { var parsed = _parseMinMaxInputs(min, max), bpMin = parsed.min, bpMax = parsed.max; if (typeof (property) !== 'string') { property = 'width'; //default to width based media query } //check what type of bp it is if (bpMin === 'retina' || bpMin === 'hdpi') { return '(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-device-pixel-ratio: 1.5)'; } else if (bpMax === 0) { return _bpMin(bpMin, property); } else if (bpMin === 0) { return _bpMax(bpMax, property); } else { return _bpBetween(bpMin, bpMax, property); } }; /** * Shortcut for the get() function that returns a height * based media query and returns the media query as a string * * @memberof DD.bp * @example * // Height of 300px and above * DD.bp.getHeight(300); * * @example * // Height of 300px and below * DD.bp.getHeight(0, 300); * * @example * // Between 100px and 300px high * DD.bp.getHeight(100, 300); * * @param {String|Number} min Breakpoint name as a string, or as a number in pixels, or in comma separated string notation * @param {String|Number} [max=0] Breakpoint name as a string, or as a number in pixels * @return {String} Media query string */ getHeight = function(min, max) { return get(min, max, 'height'); }; /** * Breakpoint function that takes the same inputs as get() but * instead of returning the media query as a string returns * if the current page matches that query as a boolean using * window.matchMedia(mq).matches * * @memberof DD.bp * @example * // returns true if the page is between xs and s * DD.bp.is('xs,s'); * DD.bp.is('xs','s'); * * @example * // returns true if the page is between 0 and 300px wide * DD.bp.is('0,300'); * DD.bp.is(0, 300); * * @param {String|Number} min Breakpoint name as a string, or as a number in pixels, or in comma separated string notation * @param {String|Number} [max=0] Breakpoint name as a string, or as a number in pixels * @param {String} [property='width'] Property to check using a media query. e.g. width or height * @return {Boolean} */ is = function(min, max, property) { if (_options.isResponsive === false) { return _bpIsValidForStatic(min, max, property); } if (window.matchMedia) { return window.matchMedia(get(min, max, property)).matches; } console.warn('DD.bp: Match Media not supported by this browser. Consider adding a polyfill.'); return false; }; /** * Shortcut for the is() function that returns a height * based media query and returns the media query as a boolean * * @memberof DD.bp * @example * // returns true if the page is between 0 and 300px high * DD.bp.isHeight('0,300'); * DD.bp.isHeight(0, 300); * * @param {String|Number} min Breakpoint name as a string, or as a number in pixels, or in comma separated string notation * @param {String|Number} [max=0] Breakpoint name as a string, or as a number in pixels * @return {Boolean} */ isHeight = function(min, max) { return is(min, max, 'height'); }; /** * Valid options for the Breakpoints array * * @typedef {Object} DD.bp.BreakpointOptions * @property {String} name Name of the breakpoint e.g. 's', 'm', 'l' * @property {Number} px Number in px for the size of the breakpoint */ /** * Valid options for the Breakpoints library * * @typedef {Object} DD.bp.Options * @property {Number} [baseFontSize] Number in px to be used as a base font size in order to calculate em values * @property {DD.bp.BreakpointOptions[]} [breakpoints] */ /** * User updatable options * * @memberof DD.bp * @example * // update the base font size only * DD.bp.options({ * baseFontSize: 14 * }); * * @example * // update the breakpoints * DD.bp.options({ * breakpoints: [ * { name: 'small', px: 400 }, * { name: 'medium', px: 800 }, * { name: 'large', px: 1200 } * ] * }); * * @param {DD.bp.Options} opts Options inside the library to be updated * @return {Boolean} */ options = function(opts) { if (typeof (opts.isResponsive) === 'boolean') { _options.isResponsive = opts.isResponsive; } if (typeof (opts.baseFontSize) === 'number') { _options.baseFontSize = opts.baseFontSize; } if (typeof (opts.breakpoints) === 'object' && opts.breakpoints.length > 0) { var isValid = true, bps = opts.breakpoints; // loop through the breakpoints to check validity for (var i = 0, len = bps.length; i < len; i += 1) { if ((bps[i].hasOwnProperty('name') && bps[i].hasOwnProperty('px')) === false) { isValid = false; } } if (isValid) { _options.breakpoints = opts.breakpoints; _initBreakpoints(); } else { console.warn('DD.bp: Invalid breakpoints array entered. Please use the format {name: \'string\', px: number}'); return false; } } return true; }; return { get: get, getHeight: getHeight, is: is, isHeight: isHeight, options: options }; }));