diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3c8b111 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig +# http://editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = tabs +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore index ef2c007..df40ca0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.sublime* +.DS_Store diff --git a/README.md b/README.md index cf750a0..ebb1811 100644 --- a/README.md +++ b/README.md @@ -13,31 +13,42 @@ vUnit is a vanilla JS microlib (~600 bytes after gzip) that allows you to size e **Second:** add the script to the `` tag and instantiate `vUnit` passing a `CSSMap` object: ```html - - + + + + + + +

This title font-size is 15% of the viewport width.

+

This p's height is 50% of the viewport height.

+

This p has some margin-top

+ ``` **Third:** Add the generated classes to your HTML elements: diff --git a/bower.json b/bower.json index 967de95..fdc6353 100644 --- a/bower.json +++ b/bower.json @@ -1,22 +1,27 @@ { - "name": "vunit", - "description": "A vanilla JS alternative to vh/vw and vmin/vmax CSS units.", - "license": "MIT", - "version": "v0.1.0", - "main": "vunit.js", - "author": "João Cunha (http://twitter.com/joaocunha)", - "homepage": "http://joaocunha.github.io/vunit/", - "keywords": [ - "js", - "viewport", - "css", - "vh", - "vw", - "vmin", - "vmax" - ], - "ignore": [ - "*.md", - "example" - ] + "name": "vunit", + "description": "A vanilla JS alternative to vh/vw and vmin/vmax CSS units.", + "license": "MIT", + "main": "src/vunit.js", + "author": "João Cunha (http://twitter.com/joaocunha)", + "homepage": "http://joaocunha.github.io/vunit/", + "moduleType": "globals", + "keywords": [ + "js", + "javascript", + "viewport", + "css", + "vh", + "vw", + "vmin", + "vmax" + ], + "ignore": [ + "*.md", + "example" + ], + "repository": { + "type": "git", + "url": "git@github.com:joaocunha/vunit.git" + } } diff --git a/dist/vunit-0.2.0.min.js b/dist/vunit-0.2.0.min.js new file mode 100644 index 0000000..4fa2ea9 --- /dev/null +++ b/dist/vunit-0.2.0.min.js @@ -0,0 +1,12 @@ +/*! + * @license MIT + * @preserve + * + * vUnit.js: A vanilla JS alternative for vh and vw CSS units. + * Version: 0.2.0 + * https://github.com/joaocunha/v-unit/ + * + * @author João Cunha - joao@joaocunha.net - twitter.com/joaocunha + */ + +;(function(win,doc,undefined){"use strict";win.vUnit=function(options){var vunit=this;var opts=options||{};vunit.options={stylesheetId:opts.stylesheetId||"v-unit-stylesheet",viewportObserverInterval:opts.viewportObserverInterval||100,CSSMap:opts.CSSMap||null,onResize:opts.onResize||function(){}};vunit.viewportSize={height:0,width:0};vunit.init=function(){if(opts.CSSMap){return win.setInterval(function viewportObserver(){if(viewportHasChanged()){var stylesheet=createStylesheet();var CSSRules=createCSSRules();appendCSSRulesToStylesheet(CSSRules,stylesheet);appendStylesheetOnHead(stylesheet);vunit.options.onResize(vunit.viewportSize)}return viewportObserver}(),vunit.options.viewportObserverInterval)}else{return false}};var viewportHasChanged=function(){var currentViewportSize=calculateViewportSize();var differentHeight=currentViewportSize.height!==vunit.viewportSize.height;var differentWidth=currentViewportSize.width!==vunit.viewportSize.width;vunit.viewportSize=currentViewportSize;return differentHeight||differentWidth};var createStylesheet=function(){var stylesheet=doc.createElement("style");stylesheet.setAttribute("rel","stylesheet");stylesheet.setAttribute("type","text/css");stylesheet.setAttribute("media","screen");stylesheet.setAttribute("id",vunit.options.stylesheetId);return stylesheet};var createCSSRules=function(){var computedHeight=vunit.viewportSize.height/100;var computedWidth=vunit.viewportSize.width/100;var vmin=Math.min(computedWidth,computedHeight);var vmax=Math.max(computedWidth,computedHeight);var map=vunit.options.CSSMap;var CSSRules="";var value=0;for(var selector in map){var property=map[selector].property;for(var range=1;range<=100;range++){switch(map[selector].reference){case"vw":value=computedWidth*range;break;case"vh":value=computedHeight*range;break;case"vmin":value=vmin*range;break;case"vmax":value=vmax*range;break}var CSSRuleTemplate="_SELECTOR__RANGE_{_PROPERTY_:_VALUE_px}\n";CSSRules+=CSSRuleTemplate.replace("_SELECTOR_",selector).replace("_RANGE_",range).replace("_PROPERTY_",property).replace("_VALUE_",value)}}return CSSRules};var appendCSSRulesToStylesheet=function(CSSRules,stylesheet){if(stylesheet.styleSheet){stylesheet.styleSheet.cssText=CSSRules}else{stylesheet.appendChild(doc.createTextNode(CSSRules))}};var appendStylesheetOnHead=function(stylesheet){var head=doc.head||doc.getElementsByTagName("head")[0]||doc.documentElement;var legacyStylesheet=doc.getElementById(vunit.options.stylesheetId);if(legacyStylesheet){head.removeChild(legacyStylesheet)}head.appendChild(stylesheet)};var calculateViewportSize=function(){var viewportSize={height:doc.documentElement.clientHeight,width:doc.documentElement.clientWidth};return viewportSize}}})(window,document); diff --git a/example/example.css b/example/example.css index 23f3c43..fe7edeb 100644 --- a/example/example.css +++ b/example/example.css @@ -1,231 +1,272 @@ @font-face { - font-family: "Flaticon"; - src: url("flaticon.eot"); - src: url("flaticon.eot#iefix") format("embedded-opentype"), - url("flaticon.woff") format("woff"), - url("flaticon.ttf") format("truetype"), - url("flaticon.svg") format("svg"); - font-weight: normal; - font-style: normal; -} -[class^="flaticon-"]:before, [class*=" flaticon-"]:before, -[class^="flaticon-"]:after, [class*=" flaticon-"]:after { - font-family: Flaticon; - font-size: inherit; - font-style: normal; + font-family: 'Flaticon'; + font-weight: normal; + font-style: normal; + src: url('flaticon.eot'); + src: url('flaticon.eot#iefix') format('embedded-opentype'), url('flaticon.woff') format('woff'), url('flaticon.ttf') format('truetype'), url('flaticon.svg') format('svg'); } + +[class^='flaticon-']:before, +[class*=' flaticon-']:before, +[class^='flaticon-']:after, +[class*=' flaticon-']:after { + font-family: Flaticon; + font-size: inherit; + font-style: normal; +} + .flaticon-feather26:before { - content: "\e000"; + content: '\e000'; } + .flaticon-github7:before { - content: "\e001"; + content: '\e001'; } + .flaticon-js1:before { - content: "\e002"; + content: '\e002'; } + .flaticon-lightning24:before { - content: "\e003"; + content: '\e003'; } + .flaticon-web40:before { - content: "\e004"; + content: '\e004'; } + i { - display: block; - text-align: center; - font-size: 80px; + font-size: 80px; + display: block; + text-align: center; } - - - - -*, *:after, *:before { - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - -ms-box-sizing: border-box; - box-sizing: border-box; - font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Geneva, Arial, sans-serif; +*, +*:after, +*:before { + font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Geneva, Arial, sans-serif; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -ms-box-sizing: border-box; } -html, body { - margin:0; - padding:0; - color: #333333; - background: #F9F9F9; +html, +body { + margin: 0; + padding: 0; + color: #333333; + background: #F9F9F9; } div { - position: relative; + position: relative; } + .vunit-pre { - color: white; - font-size: 10px; - display: none; - white-space: normal; - background: black; - position: absolute; - font-family: 'courier new', courier, monospace; - top: 5px; - left: 5px; - margin: 0; - opacity: .7; - font-weight: normal; - z-index: 9999; -} - -h1, h2 { - text-align: center; - position: relative; - margin: 0 auto; - font-family: Georgia, Serif; - line-height: 1.5; + font-family: 'courier new', courier, monospace; + font-size: 10px; + font-weight: normal; + position: absolute; + z-index: 9999; + top: 5px; + left: 5px; + display: none; + margin: 0; + white-space: normal; + opacity: .7; + color: white; + background: black; +} + +h1, +h2 { + font-family: Georgia, Serif; + line-height: 1.5; + position: relative; + margin: 0 auto; + text-align: center; } + .subtitle { - font-weight: normal; + font-weight: normal; } + h1 a { - color: #333; - font-family: Georgia, Serif; + font-family: Georgia, Serif; + color: #333333; } .white { - background: white; - float: left; + float: left; + background: white; } header { - overflow: hidden; - position: relative; + position: relative; + overflow: hidden; } + p { - line-height: 1.6; + line-height: 1.6; } + .features p { - margin: 20px auto; - text-align: center; - max-width: 250px; - padding: 0 5px; + max-width: 250px; + margin: 20px auto; + padding: 0 5px; + text-align: center; } - .debug .vunit { - box-shadow: inset 0px 0px 0px 1px fuchsia; + box-shadow: inset 0 0 0 1px fuchsia; } -.debug pre { - display: inline-block; -} -.debug * { +.debug pre { + display: inline-block; } .press-me { - background: #333333; - border: none; - position: fixed; - top: 20px; - right: 20px; - width: 80px; - height: 80px; - color: white; - font-size: 16px; - cursor: pointer; - border-radius: 50%; + font-size: 16px; + position: fixed; + top: 20px; + right: 20px; + width: 80px; + height: 80px; + cursor: pointer; + opacity: .6; + color: white; + border: none; + border-radius: 50%; + outline: none; + background: fuchsia; } + .press-me:hover { - background: #666666; + opacity: 1; } -@media (max-width: 600px) { - .vunit { - transition: all .5s; - -moz-transition: all .5s; - -webkit-transition: all .5s; - -ms-transition: all .5s; - } - .press-me { - height: 50px; - width: 50px; - font-size: 12px; - } +.mobile-notice { + display: none; + padding: 10px; + border-top: 1px solid #DDDDDD; + background: beige; } +@media (max-width: 600px) { + .vunit { + -webkit-transition: all .5s; + -moz-transition: all .5s; + -ms-transition: all .5s; + transition: all .5s; + } + + .press-me { + font-size: 12px; + width: 50px; + height: 50px; + } + + .mobile-notice { + display: block; + } +} .features { - background: white; - overflow: hidden; - padding: 20px 0; - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; + overflow: hidden; + padding: 20px 0; + border-top: 1px solid #DDDDDD; + border-bottom: 1px solid #DDDDDD; + background: white; } + .features .vunit { - float: left; + float: left; } .embed { - margin: 0 auto; - max-width: 800px; - padding: 0 10px; + max-width: 800px; + margin: 0 auto; + padding: 0 10px; +} + +.embed table * { + font-family: monospace; } .img1 { - background: url(http://41.media.tumblr.com/0aa47ca33d3e29e43e90211b782e9fc8/tumblr_ngy7usnwPo1tomxvuo6_1280.jpg) no-repeat center center; - background-size: cover; + background: url(http://41.media.tumblr.com/0aa47ca33d3e29e43e90211b782e9fc8/tumblr_ngy7usnwPo1tomxvuo6_1280.jpg) no-repeat center center; + background-size: cover; } + .img2 { - background: url(http://40.media.tumblr.com/18a4e82c6b52f42a8e02bc77e02d6156/tumblr_ng9q6igjWM1tomxvuo7_1280.jpg) no-repeat center center; - background-size: cover; + background: url(http://40.media.tumblr.com/18a4e82c6b52f42a8e02bc77e02d6156/tumblr_ng9q6igjWM1tomxvuo7_1280.jpg) no-repeat center center; + background-size: cover; } + .img3 { - background: url(http://41.media.tumblr.com/53f9c611b37971fb1e274162563ddea7/tumblr_n4e8tjQkyv1tomxvuo9_1280.jpg) no-repeat center center; - background-size: cover; + background: url(http://41.media.tumblr.com/53f9c611b37971fb1e274162563ddea7/tumblr_n4e8tjQkyv1tomxvuo9_1280.jpg) no-repeat center center; + background-size: cover; } + .img4 { - background: url(http://41.media.tumblr.com/9abc1dcb129521f7b7dbcef729f235d9/tumblr_n4e9h7r1bv1tomxvuo1_1280.jpg) no-repeat center center; - background-size: cover; + background: url(http://41.media.tumblr.com/9abc1dcb129521f7b7dbcef729f235d9/tumblr_n4e9h7r1bv1tomxvuo1_1280.jpg) no-repeat center center; + background-size: cover; } + .img5 { - background: url(http://40.media.tumblr.com/038c4956afadbb7a5dafd5621e493023/tumblr_n4e8jxX55w1tomxvuo6_1280.jpg) no-repeat center center; - background-size: cover; + background: url(http://40.media.tumblr.com/038c4956afadbb7a5dafd5621e493023/tumblr_n4e8jxX55w1tomxvuo6_1280.jpg) no-repeat center center; + background-size: cover; } + .img6 { - background: url(http://41.media.tumblr.com/c273daba85e7a4b33c844735b92d9ead/tumblr_n4e8cznbcL1tomxvuo5_1280.jpg) no-repeat center center; - background-size: cover; + background: url(http://41.media.tumblr.com/c273daba85e7a4b33c844735b92d9ead/tumblr_n4e8cznbcL1tomxvuo5_1280.jpg) no-repeat center center; + background-size: cover; } + .img7 { - background: url(http://41.media.tumblr.com/844bc240c85865cfdcf06fb9cfa11194/tumblr_n3oc1f6GKM1tomxvuo3_1280.jpg) no-repeat center center; - background-size: cover; + background: url(http://41.media.tumblr.com/844bc240c85865cfdcf06fb9cfa11194/tumblr_n3oc1f6GKM1tomxvuo3_1280.jpg) no-repeat center center; + background-size: cover; } + .img8 { - background: url(http://40.media.tumblr.com/7ce4aca550a4804183f216222d8f14e8/tumblr_n3mcekmXJV1tomxvuo10_1280.jpg) no-repeat center center; - background-size: cover; + background: url(http://40.media.tumblr.com/7ce4aca550a4804183f216222d8f14e8/tumblr_n3mcekmXJV1tomxvuo10_1280.jpg) no-repeat center center; + background-size: cover; } + .gallery { - overflow: hidden; + overflow: hidden; } + .gallery div { - float: left; + float: left; } + .works { - background: white; - margin: 0 auto; - padding: 20px; - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; -} -.works p, .works ul, .works ol { - max-width: 800px; - display: block; - margin: 20px auto; + margin: 0 auto; + padding: 20px; + border-top: 1px solid #DDDDDD; + border-bottom: 1px solid #DDDDDD; + background: white; } + +.works p, +.works ul, +.works ol { + display: block; + max-width: 800px; + margin: 20px auto; +} + li { - line-height: 2; + line-height: 2; } abbr { - border-bottom: 1px dashed #333; + border-bottom: 1px dashed #333333; } .gh { - color: #333; - text-decoration: none; + text-decoration: none; + color: #333333; } diff --git a/example/example.html b/example/example.html index 4be38c7..b84d984 100644 --- a/example/example.html +++ b/example/example.html @@ -2,186 +2,186 @@ - - - - vUnit.js - A vanilla JS alternative to buggy vh/vw/vmin/vmax CSS units. - - - - - - - - + + vUnit - a lightweight alternative to buggy vh/vw/vmin/vmax CSS units. + + + + + + + + + - - -

-
class="vwfs7 vhmt4"
- vUnit.js -

+

+
class="vwfs7 vhmt4"
+ vUnit.js +

-

-

-
class="vwfs2 vhmt4 vhmb4 vw80"
- vUnit is a vanilla JS microlib that allows you to size elements based on the viewport dimensions, without relying on the buggy vh/vw/vmin/vmax CSS units. -

- - -
-
-
class="vw25"
- -

Device agnostic: works everywhere, from IE8 to Mobile

-
- -
-
class="vw25"
- -

Lightweight: only ~600 bytes after gzip

-
- -
-
class="vw25"
- -

Super fast: doesn't play with the DOM or parse your existing CSS

-
- -
-
class="vw25"
- -

No dependencies: written in pure vanilla JS

-
-
- -

-
class="vwfs3 vhmt7 vhmb3"
- Example: viewport-based gallery -

- - - -

-
class="vwfs3 vhmt7 vhmb3"
- How it works -

-
-

Viewport relative units are awesome, except they're not - they are buggy, unreliable and have inconsistent implementation among browsers. vUnit.js offers a lightweight, robust alternative for them and weighs ~600 bytes after gzip.

- -

vUnit.js calculates the browser viewport dimensions and creates CSS rules ranging from 1% to 100% of its size. These rules are then inserted into a stylesheet which is injected on the fly in the head tag - inspect this page check the last stylesheet to see.

- -

An observer running every 100ms checks if the viewport have been resized and regenerates the CSS rules accordingly. It is a cross-device, event-less solution to keep track of everything that would trigger a resize on the viewport, namely:

- - -
- -

-
class="vwfs3 vhmt7 vhmb3"
- How to use -

- -
- -
- -

-
class="vwfs3 vhmt7 vhmb3"
- Pro tips -

-
-
    -
  1. Load vUnit on the head tag to avoid FOUC.
  2. -
  3. Add a CSS transition on mobile, so it doesn't jitter as the address bar appears/disappears.
  4. -
  5. vUnit is pretty fast, but avoid bloating your CSSMap with properties you aren't gonna use.
  6. -
  7. vUnit is not supposed to replace your grid, just to enhance your design.
  8. -
  9. Always consider non-JS users.
  10. -
-
- -

-
class="vwfs3 vhmt7 vhmb3"
- Credits -

-
-

Made with love by João Cunha

-

Awesome pictures by João Pacheco

-

Icons made by Freepik, Icomoon, Franco Averta from www.flaticon.com. Licensed by CC BY 3.0

-
-

- +

+
class="vwfs2 vhmt4 vhmb4 vw80"
+ vUnit is a vanilla JS microlib that allows you to size elements based on the viewport dimensions, without relying on the buggy vh/vw/vmin/vmax CSS units. +

+ +
+

Small viewport fellows: the example might not look that good for you. Every minute I spend improving this example page is a minute I could be improving the lib itself, so I won't go overboard on this :)

+
+ +
+
+
class="vw25"
+ +

Device agnostic: works everywhere, from IE8 to Mobile

+
+ +
+
class="vw25"
+ +

Lightweight: only ~1000 bytes after gzip

+
+ +
+
class="vw25"
+ +

Super fast: doesn't play with the DOM or parse your existing CSS

+
+ +
+
class="vw25"
+ +

No dependencies: written in pure vanilla JS

+
+
+ +

+
class="vwfs3 vhmt7 vhmb3"
+ Example: viewport-based gallery +

+ + + +

+
class="vwfs3 vhmt7 vhmb3"
+ How it works +

+
+

Viewport relative units are awesome, except they're not - they are buggy, unreliable and have inconsistent implementation among browsers. vUnit.js offers a lightweight, robust alternative for them and weighs ~600 bytes after gzip.

+ +

vUnit.js calculates the browser viewport dimensions and creates CSS rules ranging from 1% to 100% of its size. These rules are then inserted into a stylesheet which is injected on the fly in the head tag - inspect this page check the last stylesheet to see.

+ +

An observer running every 100ms checks if the viewport have been resized and regenerates the CSS rules accordingly. It is a cross-device, event-less solution to keep track of everything that would trigger a resize on the viewport, namely:

+ + +
+ +

+
class="vwfs3 vhmt7 vhmb3"
+ How to use +

+ +
+ +
+ +

+
class="vwfs3 vhmt7 vhmb3"
+ Pro tips +

+
+
    +
  1. Load vUnit on the head tag to avoid FOUC.
  2. +
  3. Add a CSS transition on mobile, so it doesn't jitter as the address bar appears/disappears.
  4. +
  5. vUnit is pretty fast, but avoid bloating your CSSMap with properties you aren't gonna use.
  6. +
  7. vUnit is not supposed to replace your grid, just to enhance your design.
  8. +
  9. Always consider non-JS users.
  10. +
+
+ +

+
class="vwfs3 vhmt7 vhmb3"
+ Credits +

+
+

Made with love by João Cunha

+

Awesome pictures by João Pacheco

+

Icons made by Freepik, Icomoon, Franco Averta from www.flaticon.com. Licensed by CC BY 3.0

+
+

+ diff --git a/example/vunit.js b/example/vunit.js deleted file mode 100644 index 5f2e423..0000000 --- a/example/vunit.js +++ /dev/null @@ -1,250 +0,0 @@ -/*! - * @license MIT - * @preserve - * - * vUnit: A vanilla JS alternative for vh and vw CSS units. - * https://github.com/joaocunha/v-unit/ - * - * @author João Cunha - joao@joaocunha.net - twitter.com/joaocunha - */ - -;(function(win, doc, undefined) { - 'use strict'; - - win.vUnit = function (options) { - // Just an alias for easier readability (and to preserve `this` context) - var vunit = this; - - // For extending the options - var opts = options || {}; - - vunit.options = { - // The ID for the appended stylesheet - stylesheetId: opts.stylesheetId || 'v-unit-stylesheet', - - // The interval between each check in miliseconds - viewportObserverInterval: opts.viewportObserverInterval || 100, - - // The CSS rules to be vUnit'd - CSSMap: opts.CSSMap || null - }; - - // Stores the viewport dimensions so the observer can check against it and update it. - vunit.viewportSize = { - height: 0, - width: 0 - }; - - /** - * @function init - * Triggers the execution of vUnit and wraps its main logic. - * - * It sets an observer to check if the viewport dimensions changed, running on an interval - * based on the viewportObserverInterval option. If the dimensions have changed, it creates - * an stylesheet, adds the calculated CSS rules to it and append it to the head. - * - * The observer is a cross-device event-less solution to keep track of everything that - * would trigger a resize on the viewport: - * - * - Window resizing on desktop; - * - Orientation changing on mobile; - * - Scrollbars appearing/disappearing on desktop; - * - Navigation bars appearing/disappearing on mobile; - * - Zooming on mobile and desktop; - * - Download bar on desktop; - * - Password saving prompt on desktop; - * - Etc. - * - * @returns {Function|Boolean} The observer function or false if no CSSMap was passed. - */ - vunit.init = function() { - // We need a CSSMap to know what rules to create. Duh! - if (opts.CSSMap) { - - // We pass a self-invoking function that returns itself to the setInterval method - // so we can execute the first iteration immediately. This helps preventing FOUC. - return win.setInterval((function viewportObserver() { - - if (viewportHasChanged()) { - var stylesheet = createStylesheet(); - var CSSRules = createCSSRules(); - appendCSSRulesToStylesheet(CSSRules, stylesheet); - appendStylesheetOnHead(stylesheet); - } - - return viewportObserver; - })(), vunit.options.viewportObserverInterval); - } else { - // Stops execution if no CSS rules were passed - // TODO: raise an exception - return false; - } - }; - - /** - * @function viewportHasChanged - * Checks if the viewport dimensions have changed since the last checking. - * - * This checking is very inexpensive, so it allows to regenerate the CSS rules only when - * it's needed. - * - * @returns {Boolean} Wether the dimensions changed or not. - */ - var viewportHasChanged = function() { - var currentViewportSize = calculateViewportSize(); - var differentHeight = (currentViewportSize.height !== vunit.viewportSize.height); - var differentWidth = (currentViewportSize.width !== vunit.viewportSize.width); - - // Updates the global variable for future checking - vunit.viewportSize = currentViewportSize; - - return (differentHeight || differentWidth); - }; - - /** - * @function createStylesheet - * Creates an empty stylesheet that will hold the v-unit rules. - * - * @returns {HTMLStyleElement} An empty stylesheet element. - */ - var createStylesheet = function() { - var stylesheet = doc.createElement('style'); - - stylesheet.setAttribute('rel', 'stylesheet'); - stylesheet.setAttribute('type', 'text/css'); - stylesheet.setAttribute('media', 'screen'); - stylesheet.setAttribute('id', vunit.options.stylesheetId); - - return stylesheet; - }; - - /** - * @function createCSSRules - * Create CSS rules based on the viewport dimensions. - * - * It loops through a map of CSS properties and creates rules ranging from 1 to 100 percent - * of its size. - * - * We used to Math.round() the values, but then we can't stack two .vw50 elements side by - * side on odd viewport widths. If we use Math.floor, we end up with a 1px gap. On the other - * hand, if we use pixel decimals (no round or floor), the browsers ajusts the width - * properly. - * - * Example: - * .vw1 {width: 20px;} - * .vw2 {width: 40px;} - * ... - * .vw100 {width: 2000px;} - * .vh1 {height: 5px;} - * .vh2 {height: 10px;} - * ... - * .vh100 {height: 500px;} - * - * @returns {String} The concatenated CSS rules in string format. - */ - var createCSSRules = function() { - var computedHeight = (vunit.viewportSize.height / 100); - var computedWidth = (vunit.viewportSize.width / 100); - var vmin = Math.min(computedWidth, computedHeight); - var vmax = Math.max(computedWidth, computedHeight); - var map = vunit.options.CSSMap; - var CSSRules = ''; - var value = 0; - - // Loop through all selectors passed on the CSSMap option - for (var selector in map) { - var property = map[selector].property; - - // Adds rules from className1 to className100 to the stylesheet - for (var range = 1; range <= 100; range++) { - - // Checks what to base the value on (viewport width/height or vmin/vmax) - switch (map[selector].reference) { - case 'vw': - value = computedWidth * range; - break; - case 'vh': - value = computedHeight * range; - break; - case 'vmin': - value = vmin * range; - break; - case 'vmax': - value = vmax * range; - break; - } - - // Simple templating syntax - var CSSRuleTemplate = '_SELECTOR__RANGE_{_PROPERTY_:_VALUE_px}\n'; - - CSSRules += CSSRuleTemplate.replace('_SELECTOR_', selector) - .replace('_RANGE_', range) - .replace('_PROPERTY_', property) - .replace('_VALUE_', value); - } // end 1-100 range loop - } // end CSSMap selectors loop - - return CSSRules; - }; - - /** - * @function appendCSSRulesToStylesheet - * Appends the created CSS rules (string) to the empty stylesheet. - * - * @param {String} CSSRules A string containing all the calculated CSS rules. - * @param {HTMLStyleElement} stylesheet An empty stylesheet object to hold the rules. - */ - var appendCSSRulesToStylesheet = function(CSSRules, stylesheet) { - // IE < 8 checking - if (stylesheet.styleSheet) { - stylesheet.styleSheet.cssText = CSSRules; - } else { - stylesheet.appendChild(doc.createTextNode(CSSRules)); - } - }; - - /** - * @function appendStylesheetOnHead - * Appends the stylesheet to the element once the CSS rules are created. - * - * @param {HTMLStyleElement} stylesheet A populated stylesheet object. - */ - var appendStylesheetOnHead = function(stylesheet) { - // Borrowed head detection from restyle.js - thanks, Andrea! - // https://github.com/WebReflection/restyle/blob/master/src/restyle.js - var head = doc.head || doc.getElementsByTagName('head')[0] || doc.documentElement; - - // Grabs the previous stylesheet - var legacyStylesheet = doc.getElementById(vunit.options.stylesheetId); - - // Removes the previous stylesheet from the head, if any - if (legacyStylesheet) { - head.removeChild(legacyStylesheet); - } - - // Add the new stylesheet to the head - head.appendChild(stylesheet); - }; - - /** - * @function calculateViewportSize - * Calculates the size of the viewport. - * - * @returns {Object} An object containing the dimensions of the viewport. - * - * Example: - * return { - * width: 768, - * height: 1024 - * } - */ - var calculateViewportSize = function() { - var viewportSize = { - height: doc.documentElement.clientHeight, - width: doc.documentElement.clientWidth - }; - - return viewportSize; - }; - }; -})(window, document); diff --git a/package.json b/package.json new file mode 100644 index 0000000..e277699 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "vunit", + "version": "0.2.0", + "description": "A vanilla JS alternative to vh/vw and vmin/vmax CSS units.", + "license": "MIT", + "repository": { + "type": "git", + "url": "git@github.com:joaocunha/vunit.git" + }, + "author": "João Cunha (http://twitter.com/joaocunha)", + "files": [ + "src/vunit.js" + ], + "keywords": [ + "js", + "javascript", + "viewport", + "css", + "vh", + "vw", + "vmin", + "vmax" + ], + "bugs": { + "url": "https://github.com/joaocunha/vunit/issues" + }, + "homepage": "http://joaocunha.github.io/vunit/", + "main": "src/vunit.js" +} diff --git a/src/vunit.js b/src/vunit.js new file mode 100644 index 0000000..1714c9a --- /dev/null +++ b/src/vunit.js @@ -0,0 +1,256 @@ +/*! + * @license MIT + * @preserve + * + * vUnit.js: A vanilla JS alternative for vh and vw CSS units. + * Version: 0.2.0 + * https://github.com/joaocunha/v-unit/ + * + * @author João Cunha - joao@joaocunha.net - twitter.com/joaocunha + */ + +;(function(win, doc, undefined) { + 'use strict'; + + win.vUnit = function (options) { + // Just an alias for easier readability (and to preserve `this` context) + var vunit = this; + + // For extending the options + var opts = options || {}; + + vunit.options = { + // The ID for the appended stylesheet + stylesheetId: opts.stylesheetId || 'v-unit-stylesheet', + + // The interval between each check in miliseconds + viewportObserverInterval: opts.viewportObserverInterval || 100, + + // The CSS rules to be vUnit'd + CSSMap: opts.CSSMap || null, + + // onResize callback + onResize: opts.onResize || function() {} + }; + + // Stores the viewport dimensions so the observer can check against it and update it. + vunit.viewportSize = { + height: 0, + width: 0 + }; + + /** + * @function init + * Triggers the execution of vUnit and wraps its main logic. + * + * It sets an observer to check if the viewport dimensions changed, running on an interval + * based on the viewportObserverInterval option. If the dimensions have changed, it creates + * an stylesheet, adds the calculated CSS rules to it and append it to the head. + * + * The observer is a cross-device event-less solution to keep track of everything that + * would trigger a resize on the viewport: + * + * - Window resizing on desktop; + * - Orientation changing on mobile; + * - Scrollbars appearing/disappearing on desktop; + * - Navigation bars appearing/disappearing on mobile; + * - Zooming on mobile and desktop; + * - Download bar on desktop; + * - Password saving prompt on desktop; + * - Etc. + * + * @returns {Function|Boolean} The observer function or false if no CSSMap was passed. + */ + vunit.init = function() { + // We need a CSSMap to know what rules to create. Duh! + if (opts.CSSMap) { + + // We pass a self-invoking function that returns itself to the setInterval method + // so we can execute the first iteration immediately. This helps preventing FOUC. + return win.setInterval((function viewportObserver() { + + if (viewportHasChanged()) { + var stylesheet = createStylesheet(); + var CSSRules = createCSSRules(); + + appendCSSRulesToStylesheet(CSSRules, stylesheet); + appendStylesheetOnHead(stylesheet); + vunit.options.onResize(vunit.viewportSize); + } + + return viewportObserver; + })(), vunit.options.viewportObserverInterval); + } else { + // Stops execution if no CSS rules were passed + // TODO: raise an exception + return false; + } + }; + + /** + * @function viewportHasChanged + * Checks if the viewport dimensions have changed since the last checking. + * + * This checking is very inexpensive, so it allows to regenerate the CSS rules only when + * it's needed. + * + * @returns {Boolean} Wether the dimensions changed or not. + */ + var viewportHasChanged = function() { + var currentViewportSize = calculateViewportSize(); + var differentHeight = (currentViewportSize.height !== vunit.viewportSize.height); + var differentWidth = (currentViewportSize.width !== vunit.viewportSize.width); + + // Updates the global variable for future checking + vunit.viewportSize = currentViewportSize; + + return (differentHeight || differentWidth); + }; + + /** + * @function createStylesheet + * Creates an empty stylesheet that will hold the v-unit rules. + * + * @returns {HTMLStyleElement} An empty stylesheet element. + */ + var createStylesheet = function() { + var stylesheet = doc.createElement('style'); + + stylesheet.setAttribute('rel', 'stylesheet'); + stylesheet.setAttribute('type', 'text/css'); + stylesheet.setAttribute('media', 'screen'); + stylesheet.setAttribute('id', vunit.options.stylesheetId); + + return stylesheet; + }; + + /** + * @function createCSSRules + * Create CSS rules based on the viewport dimensions. + * + * It loops through a map of CSS properties and creates rules ranging from 1 to 100 percent + * of its size. + * + * We used to Math.round() the values, but then we can't stack two .vw50 elements side by + * side on odd viewport widths. If we use Math.floor, we end up with a 1px gap. On the other + * hand, if we use pixel decimals (no round or floor), the browsers ajusts the width + * properly. + * + * Example: + * .vw1 {width: 20px;} + * .vw2 {width: 40px;} + * ... + * .vw100 {width: 2000px;} + * .vh1 {height: 5px;} + * .vh2 {height: 10px;} + * ... + * .vh100 {height: 500px;} + * + * @returns {String} The concatenated CSS rules in string format. + */ + var createCSSRules = function() { + var computedHeight = (vunit.viewportSize.height / 100); + var computedWidth = (vunit.viewportSize.width / 100); + var vmin = Math.min(computedWidth, computedHeight); + var vmax = Math.max(computedWidth, computedHeight); + var map = vunit.options.CSSMap; + var CSSRules = ''; + var value = 0; + + // Loop through all selectors passed on the CSSMap option + for (var selector in map) { + var property = map[selector].property; + + // Adds rules from className1 to className100 to the stylesheet + for (var range = 1; range <= 100; range++) { + + // Checks what to base the value on (viewport width/height or vmin/vmax) + switch (map[selector].reference) { + case 'vw': + value = computedWidth * range; + break; + case 'vh': + value = computedHeight * range; + break; + case 'vmin': + value = vmin * range; + break; + case 'vmax': + value = vmax * range; + break; + } + + // Barebones templating syntax + var CSSRuleTemplate = '_SELECTOR__RANGE_{_PROPERTY_:_VALUE_px}\n'; + + CSSRules += CSSRuleTemplate.replace('_SELECTOR_', selector) + .replace('_RANGE_', range) + .replace('_PROPERTY_', property) + .replace('_VALUE_', value); + } + } + + return CSSRules; + }; + + /** + * @function appendCSSRulesToStylesheet + * Appends the created CSS rules (string) to the empty stylesheet. + * + * @param {String} CSSRules A string containing all the calculated CSS rules. + * @param {HTMLStyleElement} stylesheet An empty stylesheet object to hold the rules. + */ + var appendCSSRulesToStylesheet = function(CSSRules, stylesheet) { + // IE < 8 checking + if (stylesheet.styleSheet) { + stylesheet.styleSheet.cssText = CSSRules; + } else { + stylesheet.appendChild(doc.createTextNode(CSSRules)); + } + }; + + /** + * @function appendStylesheetOnHead + * Appends the stylesheet to the element once the CSS rules are created. + * + * @param {HTMLStyleElement} stylesheet A populated stylesheet object. + */ + var appendStylesheetOnHead = function(stylesheet) { + // Borrowed head detection from restyle.js - thanks, Andrea! + // https://github.com/WebReflection/restyle/blob/master/src/restyle.js + var head = doc.head || doc.getElementsByTagName('head')[0] || doc.documentElement; + + // Grabs the previous stylesheet + var legacyStylesheet = doc.getElementById(vunit.options.stylesheetId); + + // Removes the previous stylesheet from the head, if any + if (legacyStylesheet) { + head.removeChild(legacyStylesheet); + } + + // Add the new stylesheet to the head + head.appendChild(stylesheet); + }; + + /** + * @function calculateViewportSize + * Calculates the size of the viewport. + * + * @returns {Object} An object containing the dimensions of the viewport. + * + * Example: + * return { + * width: 768, + * height: 1024 + * } + */ + var calculateViewportSize = function() { + var viewportSize = { + height: doc.documentElement.clientHeight, + width: doc.documentElement.clientWidth + }; + + return viewportSize; + }; + }; +})(window, document); diff --git a/vunit.js b/vunit.js deleted file mode 100644 index 83fe525..0000000 --- a/vunit.js +++ /dev/null @@ -1,257 +0,0 @@ -/*! - * @license MIT - * @preserve - * - * vUnit: A vanilla JS alternative for vh and vw CSS units. - * https://github.com/joaocunha/v-unit/ - * - * @author João Cunha - joao@joaocunha.net - twitter.com/joaocunha - */ - -;(function(win, doc, undefined) { - 'use strict'; - - win.vUnit = function (options) { - // Just an alias for easier readability (and to preserve `this` context) - var vunit = this; - - // For extending the options - var opts = options || {}; - - vunit.options = { - // The ID for the appended stylesheet - stylesheetId: opts.stylesheetId || 'v-unit-stylesheet', - - // The interval between each check in miliseconds - viewportObserverInterval: opts.viewportObserverInterval || 100, - - // The CSS rules to be vUnit'd - CSSMap: opts.CSSMap || null, - - // called on change - onChange: opts.onChange || null - }; - - // Stores the viewport dimensions so the observer can check against it and update it. - vunit.viewportSize = { - height: 0, - width: 0 - }; - - /** - * @function init - * Triggers the execution of vUnit and wraps its main logic. - * - * It sets an observer to check if the viewport dimensions changed, running on an interval - * based on the viewportObserverInterval option. If the dimensions have changed, it creates - * an stylesheet, adds the calculated CSS rules to it and append it to the head. - * - * The observer is a cross-device event-less solution to keep track of everything that - * would trigger a resize on the viewport: - * - * - Window resizing on desktop; - * - Orientation changing on mobile; - * - Scrollbars appearing/disappearing on desktop; - * - Navigation bars appearing/disappearing on mobile; - * - Zooming on mobile and desktop; - * - Download bar on desktop; - * - Password saving prompt on desktop; - * - Etc. - * - * @returns {Function|Boolean} The observer function or false if no CSSMap was passed. - */ - vunit.init = function() { - // We need a CSSMap to know what rules to create. Duh! - if (opts.CSSMap) { - - // We pass a self-invoking function that returns itself to the setInterval method - // so we can execute the first iteration immediately. This helps preventing FOUC. - return win.setInterval((function viewportObserver() { - - if (viewportHasChanged()) { - var stylesheet = createStylesheet(); - var CSSRules = createCSSRules(); - appendCSSRulesToStylesheet(CSSRules, stylesheet); - appendStylesheetOnHead(stylesheet); - - if (vunit.options.onChange) { - vunit.options.onChange(vunit.viewportSize); - } - } - - return viewportObserver; - })(), vunit.options.viewportObserverInterval); - } else { - // Stops execution if no CSS rules were passed - // TODO: raise an exception - return false; - } - }; - - /** - * @function viewportHasChanged - * Checks if the viewport dimensions have changed since the last checking. - * - * This checking is very inexpensive, so it allows to regenerate the CSS rules only when - * it's needed. - * - * @returns {Boolean} Wether the dimensions changed or not. - */ - var viewportHasChanged = function() { - var currentViewportSize = calculateViewportSize(); - var differentHeight = (currentViewportSize.height !== vunit.viewportSize.height); - var differentWidth = (currentViewportSize.width !== vunit.viewportSize.width); - - // Updates the global variable for future checking - vunit.viewportSize = currentViewportSize; - - return (differentHeight || differentWidth); - }; - - /** - * @function createStylesheet - * Creates an empty stylesheet that will hold the v-unit rules. - * - * @returns {HTMLStyleElement} An empty stylesheet element. - */ - var createStylesheet = function() { - var stylesheet = doc.createElement('style'); - - stylesheet.setAttribute('rel', 'stylesheet'); - stylesheet.setAttribute('type', 'text/css'); - stylesheet.setAttribute('media', 'screen'); - stylesheet.setAttribute('id', vunit.options.stylesheetId); - - return stylesheet; - }; - - /** - * @function createCSSRules - * Create CSS rules based on the viewport dimensions. - * - * It loops through a map of CSS properties and creates rules ranging from 1 to 100 percent - * of its size. - * - * We used to Math.round() the values, but then we can't stack two .vw50 elements side by - * side on odd viewport widths. If we use Math.floor, we end up with a 1px gap. On the other - * hand, if we use pixel decimals (no round or floor), the browsers ajusts the width - * properly. - * - * Example: - * .vw1 {width: 20px;} - * .vw2 {width: 40px;} - * ... - * .vw100 {width: 2000px;} - * .vh1 {height: 5px;} - * .vh2 {height: 10px;} - * ... - * .vh100 {height: 500px;} - * - * @returns {String} The concatenated CSS rules in string format. - */ - var createCSSRules = function() { - var computedHeight = (vunit.viewportSize.height / 100); - var computedWidth = (vunit.viewportSize.width / 100); - var vmin = Math.min(computedWidth, computedHeight); - var vmax = Math.max(computedWidth, computedHeight); - var map = vunit.options.CSSMap; - var CSSRules = ''; - var value = 0; - - // Loop through all selectors passed on the CSSMap option - for (var selector in map) { - var property = map[selector].property; - - // Adds rules from className1 to className100 to the stylesheet - for (var range = 1; range <= 100; range++) { - - // Checks what to base the value on (viewport width/height or vmin/vmax) - switch (map[selector].reference) { - case 'vw': - value = computedWidth * range; - break; - case 'vh': - value = computedHeight * range; - break; - case 'vmin': - value = vmin * range; - break; - case 'vmax': - value = vmax * range; - break; - } - - // Simple templating syntax - var CSSRuleTemplate = '_SELECTOR__RANGE_{_PROPERTY_:_VALUE_px}\n'; - - CSSRules += CSSRuleTemplate.replace('_SELECTOR_', selector) - .replace('_RANGE_', range) - .replace('_PROPERTY_', property) - .replace('_VALUE_', value); - } // end 1-100 range loop - } // end CSSMap selectors loop - - return CSSRules; - }; - - /** - * @function appendCSSRulesToStylesheet - * Appends the created CSS rules (string) to the empty stylesheet. - * - * @param {String} CSSRules A string containing all the calculated CSS rules. - * @param {HTMLStyleElement} stylesheet An empty stylesheet object to hold the rules. - */ - var appendCSSRulesToStylesheet = function(CSSRules, stylesheet) { - // IE < 8 checking - if (stylesheet.styleSheet) { - stylesheet.styleSheet.cssText = CSSRules; - } else { - stylesheet.appendChild(doc.createTextNode(CSSRules)); - } - }; - - /** - * @function appendStylesheetOnHead - * Appends the stylesheet to the element once the CSS rules are created. - * - * @param {HTMLStyleElement} stylesheet A populated stylesheet object. - */ - var appendStylesheetOnHead = function(stylesheet) { - // Borrowed head detection from restyle.js - thanks, Andrea! - // https://github.com/WebReflection/restyle/blob/master/src/restyle.js - var head = doc.head || doc.getElementsByTagName('head')[0] || doc.documentElement; - - // Grabs the previous stylesheet - var legacyStylesheet = doc.getElementById(vunit.options.stylesheetId); - - // Removes the previous stylesheet from the head, if any - if (legacyStylesheet) { - head.removeChild(legacyStylesheet); - } - - // Add the new stylesheet to the head - head.appendChild(stylesheet); - }; - - /** - * @function calculateViewportSize - * Calculates the size of the viewport. - * - * @returns {Object} An object containing the dimensions of the viewport. - * - * Example: - * return { - * width: 768, - * height: 1024 - * } - */ - var calculateViewportSize = function() { - var viewportSize = { - height: doc.documentElement.clientHeight, - width: doc.documentElement.clientWidth - }; - - return viewportSize; - }; - }; -})(window, document);