Comparing version 1.0.0 to 1.1.0
@@ -0,0 +0,0 @@ module.exports = { |
@@ -0,1 +1,5 @@ | ||
## [1.1.0] - 2017-05-21 | ||
### Changed | ||
- remove window resize listener and use match media listener | ||
## [1.0.0] - 2017-05-18 | ||
@@ -2,0 +6,0 @@ - Version bumped to stable after testing in production |
@@ -0,0 +0,0 @@ module.exports = function (config) { |
{ | ||
"name": "wiggle.js", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "Media query change listener", | ||
@@ -11,4 +11,3 @@ "main": "wiggle.js", | ||
"keywords": [ | ||
"media-query", | ||
"MediaQuery", | ||
"media query", | ||
"matchMedia", | ||
@@ -15,0 +14,0 @@ "responsive", |
109
README.md
# wiggle | ||
Small wrapper around [matchMedia](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia) | ||
to easily react on changes in page layout. | ||
Subscribe and react to page breakpoints. | ||
Ideal for responsive pages as it allows us to easily react with JS on page layout changes. | ||
### When to use it | ||
- We might need to swipe position of 2 elements with JS/jQuery when page layout changes from desktop to mobile layout and vice versa | ||
- We want to optimize code to include only components that are used on current screen layout (in React, Vue, Angular...). Check "examples of usage in Vue.js" at the bottom for more info | ||
- We might want to optimize page to load additional resources only if layout is desktop. | ||
- In any other case when we want to execute custom JS code on specific page layout. | ||
```javascript | ||
/** | ||
* Before using library we need to initialize it with desired breakpoint. | ||
* Init returns new wiggle instance that allow us to listen for defined screens definitions | ||
* Even thought we can have multiple instances of wiggle in 99% we should have only one instance. | ||
* In order to use wiggle we need to initialize it. | ||
* Init returns new wiggle instance that allow us to subscribe to defined rules | ||
**/ | ||
var wiggle = Wiggle.init([{ | ||
minWidth: 992, // Default unit is px. Same as writing '992px' | ||
name: 'desktop' // Name can be any string that is valid JS object property name and it have to be unique for each screen. | ||
var screen = Wiggle.init([{ | ||
name: 'desktop', // Required and unique for instance. It can be any string that is valid JS object name | ||
minWidth: 992 | ||
}, { | ||
minWidth: '768', | ||
maxWidth: '62em', // We can combine different measurements units but it does not mean we should! | ||
name: 'tablet' | ||
name: 'tablet', | ||
minWidth: '768', // Can be number or string. In case of number it defaults to px measurements unit | ||
maxWidth: '62em' // We can combine different measurements units but it does not mean we should! | ||
}, { | ||
maxWidth: 767, | ||
name: 'mobile' | ||
name: 'mobile', | ||
maxWidth: 767 | ||
}]); | ||
/** | ||
* Add subscriber for 'mobile' screen. | ||
* Subscriber will execute if current screen size is mobile and every time we switch from some other screens size to mobile. | ||
* Instead of mobile we can subscribe to any other defined screen size like 'desktop' or 'tablet' | ||
* Mobile screen size is defined during initialization of wiggle | ||
**/ | ||
wiggle.on('mobile', function() { | ||
console.log("Function that will be executed if current screen size is mobile and every time screen sizes switches to mobile"); | ||
// We are subscribing to names defined during initiation of wiggle. | ||
screen.on('mobile', function() { | ||
console.log('Function that will be executed if current screen size is mobile and every time screen size switches to mobile'); | ||
}); | ||
wiggle.on('mobile', function() { | ||
console.log("We can have multiple listeners for same screen size and each will be executed."); | ||
screen.on.change('mobile', function() { | ||
console.log('Function that will be executed every time screen size switches to mobile.'); | ||
}); | ||
wiggle.on('desktop', function() { | ||
console.log("Function that will be executed if current screen size is mobile and every time screen sizes switches to mobile."); | ||
screen.on('desktop', function() { | ||
console.log('Screen size is desktop'); | ||
}); | ||
wiggle.queueOn('mobile', function() { | ||
console.log("function that will be executed every time screen sizes switches to mobile size"); | ||
screen.off('tablet', function() { | ||
console.log('function that will be executed if screen size is not tablet and every time screen size stops being tablet'); | ||
}); | ||
wiggle.off('mobile', function() { | ||
console.log("function that will be executed if screen size is not mobile and every time screen size stops being mobile"); | ||
screen.off.change('desktop', function() { | ||
console.log('function that will be executed every time screen size stops being mobile'); | ||
}); | ||
wiggle.queueOff('mobile', function() { | ||
console.log("function that will be executed every time screen size stops being mobile"); | ||
}); | ||
if(wiggle.is('desktop')) { | ||
console.log("Current screen size is desktop"); | ||
if(screen.is('desktop')) { | ||
console.log('Current screen size is desktop'); | ||
} | ||
``` | ||
### Examples of usage in Vue.js | ||
// We can have multiple instances of wiggle | ||
var orientation = Wiggle.init([{ | ||
name: 'portrait', | ||
mediaQuery: '(orientation: portrait)' // Raw media query | ||
}, { | ||
name: 'landscape', | ||
mediaQuery: '(orientation: landscape)' | ||
}]); | ||
Basic premises is that we will have components that will need to be displayed only on desktop. | ||
We can easily hide them with media-queries and display none. Problem with hiding them like that is that components will | ||
still render and components code will still be executed which is far from ideal. | ||
orientation.on('portrait', function() { | ||
console.log('function that will be executed if screen is in portrait mode and every time screen switches to portrait mode'); | ||
}); | ||
With wiggle and v-if directive we can easily optimize that and render only components that are actually used on current screen layout. | ||
```javascript | ||
// Update view.type reactive Vuex store state with wiggle | ||
wiggle.on('desktop', () => store.dispatch('changeViewType', View.type.DESKTOP)); | ||
wiggle.on('tablet', () => store.dispatch('changeViewType', View.type.TABLET)); | ||
wiggle.on('mobile', () => store.dispatch('changeViewType', View.type.MOBILE)); | ||
// Now in our vue component we can have computed property as this | ||
computed: { | ||
isDesktop() { | ||
return this.$store.state.App.view.type === View.type.DESKTOP; | ||
} | ||
} | ||
``` | ||
The above code allow us to do something like | ||
```html | ||
<widgets-sidebar v-if="isDesktop"><widgets-sidebar> | ||
``` | ||
### Supported browsers | ||
Wiggle is using [matchMedia](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia) to detect layout changes. | ||
If you need to support browser that does not support matchMedia (IE9 and below) you need to include [matchMedia polyfill](https://github.com/paulirish/matchMedia.js) before using this library. | ||
matchMedia is not supported in IE9 and below. |
133
wiggle.js
/***************************************************** | ||
https://github.com/snovakovic/wiggle | ||
author: stefan.novakovich@gmail.com | ||
version: 0.7.0 | ||
version: 1.1.0 | ||
***************************************************/ | ||
@@ -17,5 +17,4 @@ (function(global, factory) { | ||
function Instance(screens, resizeDelay) { | ||
var doit; | ||
var activeScreens = []; | ||
function Instance(screens) { | ||
var activeScreens = {}; | ||
var subscribers = {}; | ||
@@ -29,58 +28,39 @@ var subscribeType = { | ||
Object.getOwnPropertyNames(subscribeType).forEach(function(name) { | ||
subscribers[name] = {}; | ||
Object.keys(subscribeType).forEach(function(key) { | ||
subscribers[key] = {}; | ||
}); | ||
window.addEventListener('resize', function() { | ||
clearTimeout(doit); | ||
doit = setTimeout(updateActiveScreens, resizeDelay); | ||
}, true); | ||
screens.forEach(function(screen) { | ||
var mediaQuery = constructMediaQuery(screen); | ||
var mql = window.matchMedia(mediaQuery); | ||
updateActiveScreens(); | ||
screenSwitch(screen, mql); | ||
mql.addListener(screenSwitch.bind(null, screen)); | ||
}); | ||
// Define private methods | ||
// Private methods | ||
function updateActiveScreens() { | ||
screens.forEach(function(screen) { | ||
isScreenActive(screen) | ||
? activateScreen(screen.name) | ||
: deactivateScreen(screen.name); | ||
}); | ||
function screenSwitch(screen, mql) { | ||
mql.matches ? activateScreen(screen) : deactivateScreen(screen); | ||
} | ||
function isScreenActive(screen) { | ||
if (typeof screen === 'string') { | ||
screen = getScreen(screen); | ||
} | ||
return Boolean(screen && (screen.minWidth || screen.maxWidth) && | ||
(!screen.minWidth || matchMedia('min-width', screen.minWidth)) && | ||
(!screen.maxWidth || matchMedia('max-width', screen.maxWidth))); | ||
function sizeToMediaQuery(size, prop) { | ||
size = typeof size === 'number' ? size + 'px' : size; | ||
return size ? '(' + prop + ':' + size + ')' : ''; | ||
} | ||
function activateScreen(name) { | ||
if (!activeScreens[name]) { | ||
activeScreens[name] = screen; | ||
notifySubscribers(name, subscribeType.on); | ||
function constructMediaQuery(screen) { | ||
if (screen.mediaQuery) { | ||
return screen.mediaQuery; | ||
} | ||
} | ||
function deactivateScreen(name) { | ||
if (activeScreens[name]) { | ||
delete activeScreens[name]; | ||
notifySubscribers(name, subscribeType.off); | ||
} | ||
} | ||
var minWidth = sizeToMediaQuery(screen.minWidth, 'min-width'); | ||
var maxWidth = sizeToMediaQuery(screen.maxWidth, 'max-width'); | ||
var appender = (minWidth && maxWidth) ? ' and ' : ''; | ||
function getScreen(name) { | ||
for (var i = 0; i < screens.length; i++) { | ||
if (name === screens[i].name) { | ||
return screens[i]; | ||
} | ||
} | ||
return minWidth + appender + maxWidth; | ||
} | ||
function matchMedia(property, width) { | ||
if (typeof width === 'number') { width += 'px'; } | ||
return width ? window.matchMedia('(' + property + ':' + width + ')').matches : false; | ||
function isScreenActive(name) { | ||
return Boolean(activeScreens[name]); | ||
} | ||
@@ -90,3 +70,3 @@ | ||
var screenSubscribers = subscribers[type][screenName]; | ||
if (screenSubscribers && screenSubscribers.length) { | ||
if (screenSubscribers) { | ||
screenSubscribers.forEach(function(subscriber) { | ||
@@ -98,3 +78,17 @@ subscriber.execute(); | ||
function subscribe(name, type, callback) { | ||
function activateScreen(screen) { | ||
if (!activeScreens[screen.name]) { | ||
activeScreens[screen.name] = screen; | ||
notifySubscribers(screen.name, subscribeType.on); | ||
} | ||
} | ||
function deactivateScreen(screen) { | ||
if (activeScreens[screen.name]) { | ||
delete activeScreens[screen.name]; | ||
notifySubscribers(screen.name, subscribeType.off); | ||
} | ||
} | ||
function subscribe(name, type, cb) { | ||
subscribers[type][name] = subscribers[type][name] || []; | ||
@@ -104,28 +98,28 @@ subscribers[type][name].push({ | ||
type: type, | ||
execute: callback | ||
execute: cb | ||
}); | ||
} | ||
// Export API | ||
// Public | ||
this.on = function(screenName, callback) { | ||
if (isScreenActive(screenName)) { callback(); } | ||
this.queueOn(screenName, callback); | ||
this.on = function(name, cb) { | ||
if (isScreenActive(name)) { cb(); } | ||
this.on.change(name, cb); | ||
}; | ||
this.queueOn = function(screenName, callback) { | ||
subscribe(screenName, subscribeType.on, callback); | ||
this.on.change = function(name, cb) { | ||
subscribe(name, subscribeType.on, cb); | ||
}; | ||
this.off = function(screenName, callback) { | ||
if (!isScreenActive(screenName)) { callback(); } | ||
this.queueOff(screenName, callback); | ||
this.off = function(name, cb) { | ||
if (!isScreenActive(name)) { cb(); } | ||
this.off.change(name, cb); | ||
}; | ||
this.queueOff = function(screenName, callback) { | ||
subscribe(screenName, subscribeType.off, callback); | ||
this.off.change = function(name, cb) { | ||
subscribe(name, subscribeType.off, cb); | ||
}; | ||
this.is = function(screenName) { | ||
return Boolean(activeScreens[screenName]); | ||
this.is = function(name) { | ||
return Boolean(activeScreens[name]); | ||
}; | ||
@@ -137,14 +131,13 @@ | ||
return { | ||
init: function(screens, resizeDelay) { | ||
// Validate screens | ||
init: function(screens) { | ||
var readme = 'Check readme file at https://github.com/snovakovic/wiggle for more info about configuring wiggle.'; | ||
var linkToReadme = 'Check readme file at https://github.com/snovakovic/wiggle for more info about configuring wiggle.'; | ||
if (!screens || !Array.isArray(screens)) { | ||
throw Error('Wiggle: Missing required screens array configuration. ' + linkToReadme); | ||
throw Error('Wiggle: Missing required screens array configuration. ' + readme); | ||
} | ||
screens.forEach(function(screen) { | ||
if (typeof screen !== 'object' || !screen.name || (!screen.minWidth && !screen.maxWidth)) { | ||
throw Error('Wiggle: Invalid screens configuration. ' + linkToReadme); | ||
if (typeof screen !== 'object' || !screen.name | ||
|| (!(screen.mediaQuery || screen.minWidth || screen.maxWidth))) { | ||
throw Error('Wiggle: Invalid screens configuration. ' + readme); | ||
} | ||
@@ -155,5 +148,5 @@ }); | ||
return new Instance(screens, Number(resizeDelay) || 25); | ||
return new Instance(screens); | ||
} | ||
} | ||
}))); |
describe('Wiggle', function() { | ||
var wiggle; | ||
const screens = { | ||
var screens = { | ||
desktop: { | ||
@@ -20,3 +20,3 @@ minWidth: 992, | ||
function mockActiveScreen(name) { | ||
size = screens[name].minWidth || screens[name].maxWidth; | ||
var size = screens[name].minWidth || screens[name].maxWidth; | ||
window.matchMedia = function(query) { | ||
@@ -45,5 +45,5 @@ return { | ||
expect(wiggle.on).toEqual(jasmine.any(Function)); | ||
expect(wiggle.queueOn).toEqual(jasmine.any(Function)); | ||
expect(wiggle.on.change).toEqual(jasmine.any(Function)); | ||
expect(wiggle.off).toEqual(jasmine.any(Function)); | ||
expect(wiggle.queueOff).toEqual(jasmine.any(Function)); | ||
expect(wiggle.off.change).toEqual(jasmine.any(Function)); | ||
expect(wiggle.is).toEqual(jasmine.any(Function)); | ||
@@ -108,3 +108,3 @@ }); | ||
it('queueOn/queueOff Listeners should be executed on resize', function(done) { | ||
it('on.change/off.change Listeners should be executed on resize', function(done) { | ||
var onDesktop = 0; | ||
@@ -114,4 +114,4 @@ var offDesktop = 0; | ||
// Initial screen size is on desktop | ||
wiggle.queueOn('desktop', function() { onDesktop += 1; }); | ||
wiggle.queueOff('desktop', function() { offDesktop += 1; }); | ||
wiggle.on.change('desktop', function() { onDesktop += 1; }); | ||
wiggle.off.change('desktop', function() { offDesktop += 1; }); | ||
@@ -118,0 +118,0 @@ // Queue should not be triggered on first screen size |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
11
16973
298
69