Skip to content

Instantly share code, notes, and snippets.

@rhysburnie
Last active November 28, 2024 00:49
Show Gist options
  • Select an option

  • Save rhysburnie/580cadb6a95f40dc0730 to your computer and use it in GitHub Desktop.

Select an option

Save rhysburnie/580cadb6a95f40dc0730 to your computer and use it in GitHub Desktop.
Responsive tier change watcher
(function($){
/**
* @author Rhys Burnie
* @license MIT
*
* The MIT License (MIT)
* Copyright (c) 2015 Rhys Burnie
* https://opensource.org/licenses/MIT
*
* Responsive Tier Watcher
* =======================
*
* Tl;dr
*
* Listen for tier change:
* `$.tierwatcher.on('tierchange', function(e, tier){})`
*
* Start watching or add new tier test classes
* `$.tierwatcher()`
*
* Get current tier key:
* `$.tierwatcher.tier()`
*
*
* Sets up a resize listener that tests which of the supplied test
* classes are currently visible and if the tier has changed
* fires a `tierchange` event.
*
* The visibility of the classes must be defined in css media queries
* by default the script will use the default tiers in bootstrap.
* The default tier names are as per bootstraps short tier names
* ie. `xs`, `sm`, `md` and `lg`
*
* You can add or redefine more test classes as you start the watcher.
*
* NOTE: the assumption is that the bootstrap utility classes for
* visibility exist. If this is not the case they will most
* likely test as visible.
* If you dont want the default tests define them as null in
* the setup ie. `$.tierwatcher({xs:null` etc.
*
* USAGE
* =====
*
* Start watching:
*
* `$.tierwatcher(); // watch the bootstrap tier defaults`
*
* Listen to tier change:
* The `tierchange` event will only fire as it enters a new tier
* not on every resize event
*
* ```
* $.tierwatcher.on('tierchange', function(e, tier) {
* console.log('tier is now ', tier);
* });
* ```
*
* You can also chain the listener when you start watching:
*
* ```
* $.tierwatcher().on('tierchange', function(e, tier, previousTier) {
* console.log('tier was ', previousTier, ' is now ', tier);
* });
* ```
*
* And add other listeners at other points
*
* `$.tierwatcher.on('tierchange', function(){});`
*
* NOTE: `$.tierwatcher.on` is an alias for `$(document).on('tierchange', function(){});`
* so binding on document instead will also work
*
* Check what the current tier is:
*
* `var currentTier = $.tierwatcher.tier();`
*/
var bootstrapTesters = {
xs: 'visible-xs-block',
sm: 'visible-sm-block',
md: 'visible-md-block',
lg: 'visible-lg-block'
};
var testers = {}, $testers, timer, lastKnownTier, locked = false;
function checkTiers()
{
var tier, previousTier = lastKnownTier;
for(key in testers) {
if(tier) break;
if(testers.hasOwnProperty(key)) {
if( $testers.find('.'+testers[key]).is(':visible') && lastKnownTier != key) {
tier = lastKnownTier = key;
}
}
}
if(tier) {
$testers.data('tier', tier);
$(document).trigger('tierchange', [tier, previousTier]);
}
locked = false
}
$.tierwatcher = function(tests)
{
testers = $.extend(bootstrapTesters, testers, tests);
if(!$testers) {
$testers = $('<div>').addClass('tierwatcher').css({
position: 'absolute',
overflow: 'hidden',
left: -9999,
width: 1,
height: 1
}).appendTo('body');
var resizeTimeoutMS = 50;
if(testers.hasOwnProperty('resizeTimeoutMS')) {
resizeTimeoutMS = testers.resizeTimeoutMS;
delete testers.resizeTimeoutMS;
}
$(window).on('resize', function(){
if(locked) return;
locked = true;
timer = setTimeout(checkTiers, resizeTimeoutMS);
});
}
for(key in testers) {
if(testers.hasOwnProperty(key) && typeof(testers[key]) == 'string') {
if(!$testers.find('.'+testers[key]).length) {
$testers.append('<div class="'+testers[key]+'">');
}
}
}
checkTiers();
return $.tierwatcher;
};
$.tierwatcher.tier = function()
{
return lastKnownTier;
}
// basicly an alias for `$(document).on('tierchange', fn)`
// you MUST specify the event `'tierchange'`
$.tierwatcher.on = function(evn,fn)
{
// only accepts this event
// but want user to specify for clarity
if(evn == 'tierchange') {
$(document).on(evn, fn);
}
};
}(jQuery));
@rhysburnie
Copy link
Author

Emit a 'tierchange' event (on the document) when browser width enters a new defined (mobile first) tier - argument[0] is the tier name.

Tier change is detected by checking the display of test elements off screen at left:-9999px;width:1px;height:1px; ie. uses css result as authority on what is accurate.
As long as the test tier element .is(':visible') it will test true.

The default test elements classes are bootstrap visibility classes 'visible-xs-block' to 'visible-lg-block'.

NB: obviously multiple test elements that are visible at the same time will skew results, only the first found will be the result - so be aware (there is no issue with the defaults 'xs', 'sm','md' and 'lg' as on they don't overlap).

@rhysburnie
Copy link
Author

$.teirwatcher().on('tierchange', function(e, tier, previousTier) {
  if(tier !== 'xs') {
    // do something every time it enters a tier that isn't 'xs'
  }
  if(tier === 'xs') {
    // do something when it enters 'xs'
  }
  if(tier === 'sm' && previousTier === 'xs') {
    // do something when tier has changed and it was 'xs' 
    // (generally this would be 'sm') but usefull for 'sm' things only
    // when entering from that previous breakpoint
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment