├── .gitignore ├── src ├── jquery.fs.shifter.less ├── jquery.fs.shifter-config.less ├── jquery.fs.shifter-styles.less └── jquery.fs.shifter.js ├── bower.json ├── README.md ├── shifter.jquery.json ├── LICENSE.md ├── package.json ├── jquery.fs.shifter.min.js ├── jquery.fs.shifter.min.css ├── demo └── index.html ├── jquery.fs.shifter.css ├── jquery.fs.shifter.js └── Gruntfile.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* -------------------------------------------------------------------------------- /src/jquery.fs.shifter.less: -------------------------------------------------------------------------------- 1 | 2 | @import "jquery.fs.shifter-config.less"; 3 | @import "jquery.fs.shifter-styles.less"; -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Shifter", 3 | "version": "3.1.2", 4 | "description": "A jQuery plugin for simple slide-out mobile navigation. Part of the Formstone Library.", 5 | "author": { 6 | "name": "Ben Plum", 7 | "email": "mr@benplum.com", 8 | "url": "http://www.benplum.com" 9 | }, 10 | "license": "MIT", 11 | "homepage": "http://classic.formstone.it/shifter/", 12 | "main": [ 13 | "jquery.fs.shifter.js", 14 | "jquery.fs.shifter.css" 15 | ], 16 | "ignore": [ 17 | "*.jquery.json" 18 | ] 19 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Development of this plugin has ended. Please upgrade to the new Formstone.


2 | 3 | Built with Grunt 4 | # Shifter 5 | 6 | A jQuery plugin for simple slide-out mobile navigation. Part of the Formstone Library. 7 | 8 | - [Demo](http://classic.formstone.it/components/Shifter/demo/index.html) 9 | - [Documentation](http://classic.formstone.it/shifter/) 10 | 11 | #### Bower Support 12 | `bower install Shifter` -------------------------------------------------------------------------------- /src/jquery.fs.shifter-config.less: -------------------------------------------------------------------------------- 1 | 2 | // Use these variables when compiling directly 3 | 4 | @shifter-transition-speed: 0.2s; // 0.2s 5 | @shifter-transition-timing: ease; // ease 6 | 7 | // Nav 8 | 9 | @shifter-nav-background: #fff; // #fff 10 | @shifter-nav-width: 270px; // 270px 11 | 12 | // Page 13 | 14 | @shifter-page-background: #fff; // #fff 15 | @shifter-page-box-shadow-color: rgba(0, 0, 0, 0.15); // rgba(0, 0, 0, 0.15) 16 | @shifter-page-box-shadow-blur: 2px; // 2px 17 | 18 | // Handle 19 | 20 | @shifter-handle-background: #fff; // #fff 21 | @shifter-handle-height: 30px; // 30px 22 | @shifter-handle-width: 30px; // 30px 23 | 24 | // Handle Icon 25 | 26 | @shifter-icon-color: #666; // #666 27 | @shifter-icon-height: 3px; // 3px 28 | @shifter-icon-width: 20px; // 20px -------------------------------------------------------------------------------- /shifter.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shifter", 3 | "version": "3.1.2", 4 | "title": "Shifter", 5 | "author": { 6 | "name": "Ben Plum", 7 | "email": "mr@benplum.com", 8 | "url": "http://www.benplum.com" 9 | }, 10 | "licenses": [ 11 | { 12 | "type": "MIT", 13 | "url": "http://opensource.org/licenses/MIT" 14 | } 15 | ], 16 | "dependencies": { 17 | "jquery": ">=1.7" 18 | }, 19 | "description": "A jQuery plugin for simple slide-out mobile navigation. Part of the Formstone Library.", 20 | "keywords": [ 21 | "responsive", 22 | "mobile", 23 | "navigation", 24 | "menu", 25 | "ui", 26 | "javascript", 27 | "jquery", 28 | "formstone" 29 | ], 30 | "docs": "http://classic.formstone.it/shifter/", 31 | "demo": "http://classic.formstone.it/shifter/", 32 | "download": "https://github.com/FormstoneClassic/Shifter", 33 | "bugs": "https://github.com/FormstoneClassic/Shifter/issues", 34 | "homepage": "http://classic.formstone.it/shifter/" 35 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2015 Ben Plum 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Shifter", 3 | "id": "shifter", 4 | "codename": "jquery.fs.shifter", 5 | "version": "3.1.2", 6 | "description": "A jQuery plugin for simple slide-out mobile navigation. Part of the Formstone Library.", 7 | "keywords": [ 8 | "responsive", 9 | "mobile", 10 | "navigation", 11 | "menu", 12 | "ui", 13 | "javascript", 14 | "jquery", 15 | "formstone" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/FormstoneClassic/Shifter" 20 | }, 21 | "homepage": "http://classic.formstone.it/shifter/", 22 | "demo": "http://classic.formstone.it/components/Shifter/demo/index.html", 23 | "license": "MIT", 24 | "author": { 25 | "name": "Ben Plum", 26 | "email": "mr@benplum.com", 27 | "url": "http://www.benplum.com" 28 | }, 29 | "devDependencies": { 30 | "grunt": "~0.4.2", 31 | "grunt-autoprefixer": "^1.0.1", 32 | "grunt-banner": "^0.2.3", 33 | "grunt-contrib-copy": "^0.6.0", 34 | "grunt-contrib-jshint": "~0.7.2", 35 | "grunt-contrib-less": "^0.11.4", 36 | "grunt-contrib-uglify": "~0.2.7", 37 | "grunt-contrib-watch": "^0.6.1", 38 | "grunt-jquerymanifest": "~0.1.3", 39 | "grunt-npm2bower-sync": "~0.3.0" 40 | } 41 | } -------------------------------------------------------------------------------- /jquery.fs.shifter.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Shifter v3.1.2 - 2015-04-04 3 | * A jQuery plugin for simple slide-out mobile navigation. Part of the Formstone Library. 4 | * http://classic.formstone.it/shifter/ 5 | * 6 | * Copyright 2015 Ben Plum; MIT Licensed 7 | */ 8 | 9 | !function(a,b){"use strict";function c(c){i||(k=a.extend({},s,c||{}),k.$html=a("html"),k.$body=a("body"),k.$shifts=a([g(m),g(n)].join(", ")),k.$nav=a(g(o)),k.$shifts.length>0&&k.$nav.length>0&&(i=!0,k.$body.on(r,g(l),e),void 0!==b.matchMedia&&(k.mediaQuery=b.matchMedia("(max-width:"+(k.maxWidth===1/0?"100000px":k.maxWidth)+")"),k.mediaQuery.addListener(d),d())))}function d(){k.mediaQuery.matches?t.enable():t.disable()}function e(a){a.preventDefault(),a.stopPropagation(),j||(k.$body.hasClass(q)?t.close():t.open()),"touchstart"===a.type&&(j=!0,setTimeout(f,500))}function f(){j=!1}function g(a){return"."+a}var h="shifter",i=!1,j=!1,k={},l=h+"-handle",m=h+"-page",n=h+"-header",o=h+"-navigation",p=h+"-enabled",q=h+"-open",r="touchstart."+h+" click."+h,s={maxWidth:"980px"},t={close:function(){i&&(k.$html.removeClass(q),k.$body.removeClass(q),k.$shifts.off(g(h)),k.$nav.find("input").trigger("blur"))},enable:function(){i&&k.$body.addClass(p)},destroy:function(){i&&(k.$html.removeClass(q),k.$body.removeClass([p,q].join(" ")).off(r),void 0!==b.matchMedia&&k.mediaQuery.removeListener(d),k={},i=!1)},disable:function(){i&&(t.close(),k.$body.removeClass(p))},open:function(){i&&(k.$html.addClass(q),k.$body.addClass(q),k.$shifts.one(r,e))}};a[h]=function(a){return t[a]?t[a].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof a&&a?this:c.apply(this,arguments)}}(jQuery,window); -------------------------------------------------------------------------------- /jquery.fs.shifter.min.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Shifter v3.1.2 - 2015-04-04 3 | * A jQuery plugin for simple slide-out mobile navigation. Part of the Formstone Library. 4 | * http://classic.formstone.it/shifter/ 5 | * 6 | * Copyright 2015 Ben Plum; MIT Licensed 7 | */ 8 | 9 | .shifter-open{overflow:hidden}.shifter-open .shifter-page *,.shifter-open .shifter-header *{pointer-events:none}.shifter-navigation{display:none;opacity:0}.shifter-handle{display:none}.shifter-enabled .shifter-page{min-height:100%;position:relative;z-index:1;background:#fff;box-shadow:2px 0 2px rgba(0,0,0,.15);display:block;overflow:hidden}.shifter-enabled.shifter-left .shifter-page{box-shadow:-2px 0 2px rgba(0,0,0,.15)}.shifter-enabled .shifter-navigation{width:270px;height:100%;position:fixed;top:0;z-index:0;background:#fff;display:block;overflow:auto;pointer-events:none;-webkit-transition:opacity .001s linear .2s,-webkit-transform .2s ease;transition:opacity .001s linear .2s,transform .2s ease}.shifter-enabled .shifter-page,.shifter-enabled .shifter-header,.shifter-enabled .shifter-navigation{-webkit-backface-visibility:hidden;backface-visibility:hidden}.shifter-enabled .shifter-page,.shifter-enabled .shifter-header{-webkit-transform:translate3D(0,0,0);-ms-transform:translate3D(0,0,0);transform:translate3D(0,0,0);-webkit-transition:-webkit-transform .2s ease;transition:transform .2s ease}.shifter-enabled .shifter-handle{width:30px;height:30px;position:relative;background:#fff;cursor:pointer;display:block;overflow:hidden;text-indent:200%;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.shifter-enabled .shifter-handle:after{height:3px;width:20px;position:absolute;top:0;right:0;bottom:0;left:0;background:#666;box-shadow:0 5px 0 #666,0 -5px 0 #666;content:'';display:block;margin:auto}.shifter-enabled .shifter-navigation{right:0;-webkit-transform:translate3D(0,0,0);-ms-transform:translate3D(0,0,0);transform:translate3D(0,0,0)}.shifter-open .shifter-page,.shifter-open .shifter-header{-webkit-transform:translate3D(-270px,0,0);-ms-transform:translate3D(-270px,0,0);transform:translate3D(-270px,0,0)}.shifter-enabled.shifter-left .shifter-navigation{left:0;-webkit-transform:translate3D(0,0,0);-ms-transform:translate3D(0,0,0);transform:translate3D(0,0,0)}.shifter-enabled.shifter-left.shifter-open .shifter-page,.shifter-enabled.shifter-left.shifter-open .shifter-header{-webkit-transform:translate3D(270px,0,0);-ms-transform:translate3D(270px,0,0);transform:translate3D(270px,0,0)}.shifter-open .shifter-navigation{opacity:1;pointer-events:all;-webkit-transform:translate3D(0,0,0);-ms-transform:translate3D(0,0,0);transform:translate3D(0,0,0);-webkit-transition:opacity .001s linear 0s,-webkit-transform .2s ease;transition:opacity .001s linear 0s,transform .2s ease}.no-csstransforms3d .shifter-enabled.shifter-navigation{right:-270px}.no-csstransforms3d .shifter-enabled.shifter-left .shifter-navigation{left:-270px}.no-csstransforms3d .shifter-enabled.shifter-open .shifter-page{left:-270px}.no-csstransforms3d .shifter-enabled.shifter-open .shifter-navigation{right:0}.no-csstransforms3d .shifter-enabled.shifter-left.shifter-open .shifter-page{left:auto;right:-270px}.no-csstransforms3d .shifter-enabled.shifter-left.shifter-open .shifter-navigation{left:0} -------------------------------------------------------------------------------- /src/jquery.fs.shifter-styles.less: -------------------------------------------------------------------------------- 1 | 2 | .shifter { 3 | &-open { 4 | overflow: hidden; 5 | } 6 | 7 | &-open &-page *, 8 | &-open &-header * { 9 | pointer-events: none; 10 | } 11 | 12 | // default 13 | 14 | &-navigation { 15 | display: none; 16 | opacity: 0; 17 | } 18 | 19 | &-handle { 20 | display: none; 21 | } 22 | 23 | // enabled 24 | 25 | &-enabled &-page { 26 | min-height: 100%; 27 | 28 | position: relative; 29 | z-index: 1; 30 | 31 | background: @shifter-page-background; 32 | box-shadow: @shifter-page-box-shadow-blur 0 @shifter-page-box-shadow-blur @shifter-page-box-shadow-color; 33 | display: block; 34 | overflow: hidden; 35 | } 36 | 37 | &-enabled&-left &-page { 38 | box-shadow: -(@shifter-page-box-shadow-blur) 0 @shifter-page-box-shadow-blur @shifter-page-box-shadow-color; 39 | } 40 | 41 | &-enabled &-navigation { 42 | width: @shifter-nav-width; 43 | height: 100%; 44 | 45 | position: fixed; 46 | top: 0; 47 | z-index: 0; 48 | 49 | background: @shifter-nav-background; 50 | display: block; 51 | // opacity: 1; 52 | overflow: auto; 53 | pointer-events: none; 54 | transition: 55 | opacity 0.001s linear @shifter-transition-speed, // delay when closing 56 | transform @shifter-transition-speed @shifter-transition-timing; 57 | } 58 | 59 | &-enabled &-page, 60 | &-enabled &-header, 61 | &-enabled &-navigation { 62 | backface-visibility: hidden; 63 | } 64 | 65 | &-enabled &-page, 66 | &-enabled &-header { 67 | transform: translate3D(0, 0, 0); 68 | transition: transform @shifter-transition-speed @shifter-transition-timing; 69 | } 70 | 71 | &-enabled &-handle { 72 | width: @shifter-handle-width; 73 | height: @shifter-handle-height; 74 | 75 | position: relative; 76 | 77 | background: @shifter-handle-background; 78 | cursor: pointer; 79 | display: block; 80 | overflow: hidden; 81 | text-indent: 200%; 82 | white-space: nowrap; 83 | user-select: none; 84 | 85 | &:after { 86 | height: @shifter-icon-height; 87 | width: @shifter-icon-width; 88 | 89 | position: absolute; 90 | top: 0; 91 | right: 0; 92 | bottom: 0; 93 | left: 0; 94 | 95 | background: @shifter-icon-color; 96 | box-shadow: 97 | 0 (@shifter-icon-height + 2) 0 @shifter-icon-color, 98 | 0 -(@shifter-icon-height + 2) 0 @shifter-icon-color; 99 | content: ''; 100 | display: block; 101 | margin: auto; 102 | } 103 | } 104 | 105 | // right (default) 106 | 107 | &-enabled &-navigation { 108 | right: 0; 109 | 110 | transform: translate3D(0, 0, 0); 111 | } 112 | 113 | &-open &-page, 114 | &-open &-header { 115 | transform: translate3D(-(@shifter-nav-width), 0, 0); 116 | } 117 | 118 | // left 119 | 120 | &-enabled&-left &-navigation { 121 | left: 0; 122 | 123 | transform: translate3D(0, 0, 0); 124 | } 125 | 126 | &-enabled&-left&-open &-page, 127 | &-enabled&-left&-open &-header { 128 | transform: translate3D(@shifter-nav-width, 0, 0); 129 | } 130 | 131 | // open 132 | &-open &-navigation { 133 | opacity: 1; 134 | pointer-events: all; 135 | transform: translate3D(0, 0, 0); 136 | transition: 137 | opacity 0.001s linear 0s, // no delay when opening 138 | transform @shifter-transition-speed @shifter-transition-timing; 139 | } 140 | 141 | 142 | // IE8 / 9 - no 3d transforms 143 | 144 | .no-csstransforms3d &-enabled&-navigation { 145 | right: -@shifter-nav-width; 146 | } 147 | 148 | // left 149 | 150 | .no-csstransforms3d &-enabled&-left &-navigation { 151 | left: -@shifter-nav-width; 152 | } 153 | 154 | // open 155 | 156 | .no-csstransforms3d &-enabled&-open &-page { 157 | left: -@shifter-nav-width; 158 | } 159 | 160 | .no-csstransforms3d &-enabled&-open &-navigation { 161 | right: 0; 162 | } 163 | 164 | // open left 165 | 166 | .no-csstransforms3d &-enabled&-left&-open &-page { 167 | left: auto; 168 | right: -@shifter-nav-width; 169 | } 170 | 171 | .no-csstransforms3d &-enabled&-left&-open &-navigation { 172 | left: 0; 173 | } 174 | 175 | } -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Shifter Demo 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 21 | 25 | 26 | 27 | 28 | 29 | 34 | 35 | 42 | 43 | 44 | 52 |
53 |
54 |
55 |
56 |

Shifter Demo

57 | 58 |
59 | View Documentation 60 |
61 | 62 | 63 | 64 | 65 |

Basic

66 |

Shifter works by checking for target elements in the DOM and binding events to them. This allows you to markup and style pages anyway you'd like, but at a minimum you need three specifically classed elements (including the body tag):

67 | 68 |
$.shifter();
69 |
<body class="shifter">
 70 | 	<div class="shifter-page">
 71 | 		<span class="shifter-handle">Menu</span>
 72 | 		<!-- Page Content -->
 73 | 	</div>
 74 | 	<nav class="shifter-navigation">
 75 | 		<!-- Navigation -->
 76 | 	</nav>
 77 | </body>
78 | 79 | 83 | 84 | 85 |

Breakpoint

86 |

By default, Shifter will enable itself on screens smaller then 980 pixels wide. You can specify a different width by setting the maxWidth option on initialization.

87 | 88 |
$.shifter({
 89 | 	maxWidth: Infinity
 90 | });
91 | 92 |
Demo
93 |

Click or tap the handle at the top-right of this page!

94 |
95 | 96 | 97 |

IE Support

98 |

When supporting IE you will need to include a HTML5 enabler and matchMedia polyfill (IE 8, IE 9).

99 | 100 | 101 | 102 |
103 |
104 | 105 | 110 |
111 | 118 | 119 | -------------------------------------------------------------------------------- /jquery.fs.shifter.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Shifter v3.1.2 - 2015-04-04 3 | * A jQuery plugin for simple slide-out mobile navigation. Part of the Formstone Library. 4 | * http://classic.formstone.it/shifter/ 5 | * 6 | * Copyright 2015 Ben Plum; MIT Licensed 7 | */ 8 | 9 | 10 | .shifter-open { 11 | overflow: hidden; 12 | } 13 | .shifter-open .shifter-page *, 14 | .shifter-open .shifter-header * { 15 | pointer-events: none; 16 | } 17 | .shifter-navigation { 18 | display: none; 19 | opacity: 0; 20 | } 21 | .shifter-handle { 22 | display: none; 23 | } 24 | .shifter-enabled .shifter-page { 25 | min-height: 100%; 26 | position: relative; 27 | z-index: 1; 28 | background: #ffffff; 29 | box-shadow: 2px 0 2px rgba(0, 0, 0, 0.15); 30 | display: block; 31 | overflow: hidden; 32 | } 33 | .shifter-enabled.shifter-left .shifter-page { 34 | box-shadow: -2px 0 2px rgba(0, 0, 0, 0.15); 35 | } 36 | .shifter-enabled .shifter-navigation { 37 | width: 270px; 38 | height: 100%; 39 | position: fixed; 40 | top: 0; 41 | z-index: 0; 42 | background: #ffffff; 43 | display: block; 44 | overflow: auto; 45 | pointer-events: none; 46 | -webkit-transition: opacity 0.001s linear 0.2s, -webkit-transform 0.2s ease; 47 | transition: opacity 0.001s linear 0.2s, transform 0.2s ease; 48 | } 49 | .shifter-enabled .shifter-page, 50 | .shifter-enabled .shifter-header, 51 | .shifter-enabled .shifter-navigation { 52 | -webkit-backface-visibility: hidden; 53 | backface-visibility: hidden; 54 | } 55 | .shifter-enabled .shifter-page, 56 | .shifter-enabled .shifter-header { 57 | -webkit-transform: translate3D(0, 0, 0); 58 | -ms-transform: translate3D(0, 0, 0); 59 | transform: translate3D(0, 0, 0); 60 | -webkit-transition: -webkit-transform 0.2s ease; 61 | transition: transform 0.2s ease; 62 | } 63 | .shifter-enabled .shifter-handle { 64 | width: 30px; 65 | height: 30px; 66 | position: relative; 67 | background: #ffffff; 68 | cursor: pointer; 69 | display: block; 70 | overflow: hidden; 71 | text-indent: 200%; 72 | white-space: nowrap; 73 | -webkit-user-select: none; 74 | -moz-user-select: none; 75 | -ms-user-select: none; 76 | user-select: none; 77 | } 78 | .shifter-enabled .shifter-handle:after { 79 | height: 3px; 80 | width: 20px; 81 | position: absolute; 82 | top: 0; 83 | right: 0; 84 | bottom: 0; 85 | left: 0; 86 | background: #666666; 87 | box-shadow: 0 5px 0 #666666, 0 -5px 0 #666666; 88 | content: ''; 89 | display: block; 90 | margin: auto; 91 | } 92 | .shifter-enabled .shifter-navigation { 93 | right: 0; 94 | -webkit-transform: translate3D(0, 0, 0); 95 | -ms-transform: translate3D(0, 0, 0); 96 | transform: translate3D(0, 0, 0); 97 | } 98 | .shifter-open .shifter-page, 99 | .shifter-open .shifter-header { 100 | -webkit-transform: translate3D(-270px, 0, 0); 101 | -ms-transform: translate3D(-270px, 0, 0); 102 | transform: translate3D(-270px, 0, 0); 103 | } 104 | .shifter-enabled.shifter-left .shifter-navigation { 105 | left: 0; 106 | -webkit-transform: translate3D(0, 0, 0); 107 | -ms-transform: translate3D(0, 0, 0); 108 | transform: translate3D(0, 0, 0); 109 | } 110 | .shifter-enabled.shifter-left.shifter-open .shifter-page, 111 | .shifter-enabled.shifter-left.shifter-open .shifter-header { 112 | -webkit-transform: translate3D(270px, 0, 0); 113 | -ms-transform: translate3D(270px, 0, 0); 114 | transform: translate3D(270px, 0, 0); 115 | } 116 | .shifter-open .shifter-navigation { 117 | opacity: 1; 118 | pointer-events: all; 119 | -webkit-transform: translate3D(0, 0, 0); 120 | -ms-transform: translate3D(0, 0, 0); 121 | transform: translate3D(0, 0, 0); 122 | -webkit-transition: opacity 0.001s linear 0s, -webkit-transform 0.2s ease; 123 | transition: opacity 0.001s linear 0s, transform 0.2s ease; 124 | } 125 | .no-csstransforms3d .shifter-enabled.shifter-navigation { 126 | right: -270px; 127 | } 128 | .no-csstransforms3d .shifter-enabled.shifter-left .shifter-navigation { 129 | left: -270px; 130 | } 131 | .no-csstransforms3d .shifter-enabled.shifter-open .shifter-page { 132 | left: -270px; 133 | } 134 | .no-csstransforms3d .shifter-enabled.shifter-open .shifter-navigation { 135 | right: 0; 136 | } 137 | .no-csstransforms3d .shifter-enabled.shifter-left.shifter-open .shifter-page { 138 | left: auto; 139 | right: -270px; 140 | } 141 | .no-csstransforms3d .shifter-enabled.shifter-left.shifter-open .shifter-navigation { 142 | left: 0; 143 | } 144 | -------------------------------------------------------------------------------- /src/jquery.fs.shifter.js: -------------------------------------------------------------------------------- 1 | ;(function ($, window) { 2 | "use strict"; 3 | 4 | var namespace = "shifter", 5 | initialized = false, 6 | hasTouched = false, 7 | data = {}, 8 | // Classes 9 | class_handle = namespace + "-handle", 10 | class_page = namespace + "-page", 11 | class_header = namespace + "-header", 12 | class_navigation = namespace + "-navigation", 13 | class_isEnabled = namespace + "-enabled", 14 | class_isOpen = namespace + "-open", 15 | // Events - Listen 16 | event_clickTouchStart = "touchstart." + namespace + " click." + namespace; 17 | 18 | /** 19 | * @options 20 | * @param maxWidth [string] <'980px'> "Width at which to auto-disable plugin" 21 | */ 22 | var options = { 23 | maxWidth: "980px" 24 | }; 25 | 26 | var pub = { 27 | 28 | /** 29 | * @method 30 | * @name close 31 | * @description Closes navigation if open 32 | * @example $.shifter("close"); 33 | */ 34 | close: function() { 35 | if (initialized) { 36 | data.$html.removeClass(class_isOpen); 37 | data.$body.removeClass(class_isOpen); 38 | data.$shifts.off( classify(namespace) ); 39 | // Close mobile keyboard if open 40 | data.$nav.find("input").trigger("blur"); 41 | } 42 | }, 43 | 44 | /** 45 | * @method 46 | * @name enable 47 | * @description Enables navigation system 48 | * @example $.shifter("enable"); 49 | */ 50 | enable: function() { 51 | if (initialized) { 52 | data.$body.addClass(class_isEnabled); 53 | } 54 | }, 55 | 56 | /** 57 | * @method 58 | * @name destroy 59 | * @description Removes instance of plugin 60 | * @example $.shifter("destroy"); 61 | */ 62 | destroy: function() { 63 | if (initialized) { 64 | data.$html.removeClass(class_isOpen); 65 | data.$body.removeClass( [class_isEnabled, class_isOpen].join(" ") ) 66 | .off(event_clickTouchStart); 67 | 68 | // Navtive MQ Support 69 | if (window.matchMedia !== undefined) { 70 | data.mediaQuery.removeListener(onRespond); 71 | } 72 | 73 | data = {}; 74 | initialized = false; 75 | } 76 | }, 77 | 78 | /** 79 | * @method 80 | * @name disable 81 | * @description Disables navigation system 82 | * @example $.shifter("disable"); 83 | */ 84 | disable: function() { 85 | if (initialized) { 86 | pub.close(); 87 | data.$body.removeClass(class_isEnabled); 88 | } 89 | }, 90 | 91 | /** 92 | * @method 93 | * @name open 94 | * @description Opens navigation if closed 95 | * @example $.shifter("open"); 96 | */ 97 | open: function() { 98 | if (initialized) { 99 | data.$html.addClass(class_isOpen); 100 | data.$body.addClass(class_isOpen); 101 | data.$shifts.one(event_clickTouchStart, onClickTouchStart); 102 | } 103 | } 104 | }; 105 | 106 | /** 107 | * @method private 108 | * @name init 109 | * @description Initializes plugin 110 | * @param opts [object] "Initialization options" 111 | */ 112 | function init(opts) { 113 | if (!initialized) { 114 | data = $.extend({}, options, opts || {}); 115 | 116 | data.$html = $("html"); 117 | data.$body = $("body"); 118 | data.$shifts = $( [classify(class_page), classify(class_header)].join(", ") ); 119 | data.$nav = $( classify(class_navigation) ); 120 | 121 | if (data.$shifts.length > 0 && data.$nav.length > 0) { 122 | initialized = true; 123 | 124 | data.$body.on(event_clickTouchStart, classify(class_handle), onClickTouchStart); 125 | 126 | // Navtive MQ Support 127 | if (window.matchMedia !== undefined) { 128 | data.mediaQuery = window.matchMedia("(max-width:" + (data.maxWidth === Infinity ? "100000px" : data.maxWidth) + ")"); 129 | data.mediaQuery.addListener(onRespond); 130 | onRespond(); 131 | } 132 | } 133 | } 134 | } 135 | 136 | /** 137 | * @method private 138 | * @name onRespond 139 | * @description Handles media query match change 140 | */ 141 | function onRespond() { 142 | if (data.mediaQuery.matches) { 143 | pub.enable(); 144 | } else { 145 | pub.disable(); 146 | } 147 | } 148 | 149 | /** 150 | * @method private 151 | * @name onClickTouchStart 152 | * @description Determines proper click / touch action 153 | * @param e [object] "Event data" 154 | */ 155 | function onClickTouchStart(e) { 156 | e.preventDefault(); 157 | e.stopPropagation(); 158 | 159 | if (!hasTouched) { 160 | if (data.$body.hasClass(class_isOpen)) { 161 | pub.close(); 162 | } else { 163 | pub.open(); 164 | } 165 | } 166 | 167 | if (e.type === "touchstart") { 168 | hasTouched = true; 169 | 170 | setTimeout(resetTouch, 500); 171 | } 172 | } 173 | 174 | /** 175 | * @method private 176 | * @name resetTouch 177 | * @description Resets touch state 178 | */ 179 | function resetTouch() { 180 | hasTouched = false; 181 | } 182 | 183 | /** 184 | * @method private 185 | * @name classify 186 | * @description Create class selector from text 187 | * @param text [string] "Text to convert" 188 | * @return [string] "New class name" 189 | */ 190 | function classify(text) { 191 | return "." + text; 192 | } 193 | 194 | $[namespace] = function(method) { 195 | if (pub[method]) { 196 | return pub[method].apply(this, Array.prototype.slice.call(arguments, 1)); 197 | } else if (typeof method === 'object' || !method) { 198 | return init.apply(this, arguments); 199 | } 200 | return this; 201 | }; 202 | })(jQuery, window); -------------------------------------------------------------------------------- /jquery.fs.shifter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Shifter v3.1.2 - 2015-04-04 3 | * A jQuery plugin for simple slide-out mobile navigation. Part of the Formstone Library. 4 | * http://classic.formstone.it/shifter/ 5 | * 6 | * Copyright 2015 Ben Plum; MIT Licensed 7 | */ 8 | 9 | ;(function ($, window) { 10 | "use strict"; 11 | 12 | var namespace = "shifter", 13 | initialized = false, 14 | hasTouched = false, 15 | data = {}, 16 | // Classes 17 | class_handle = namespace + "-handle", 18 | class_page = namespace + "-page", 19 | class_header = namespace + "-header", 20 | class_navigation = namespace + "-navigation", 21 | class_isEnabled = namespace + "-enabled", 22 | class_isOpen = namespace + "-open", 23 | // Events - Listen 24 | event_clickTouchStart = "touchstart." + namespace + " click." + namespace; 25 | 26 | /** 27 | * @options 28 | * @param maxWidth [string] <'980px'> "Width at which to auto-disable plugin" 29 | */ 30 | var options = { 31 | maxWidth: "980px" 32 | }; 33 | 34 | var pub = { 35 | 36 | /** 37 | * @method 38 | * @name close 39 | * @description Closes navigation if open 40 | * @example $.shifter("close"); 41 | */ 42 | close: function() { 43 | if (initialized) { 44 | data.$html.removeClass(class_isOpen); 45 | data.$body.removeClass(class_isOpen); 46 | data.$shifts.off( classify(namespace) ); 47 | // Close mobile keyboard if open 48 | data.$nav.find("input").trigger("blur"); 49 | } 50 | }, 51 | 52 | /** 53 | * @method 54 | * @name enable 55 | * @description Enables navigation system 56 | * @example $.shifter("enable"); 57 | */ 58 | enable: function() { 59 | if (initialized) { 60 | data.$body.addClass(class_isEnabled); 61 | } 62 | }, 63 | 64 | /** 65 | * @method 66 | * @name destroy 67 | * @description Removes instance of plugin 68 | * @example $.shifter("destroy"); 69 | */ 70 | destroy: function() { 71 | if (initialized) { 72 | data.$html.removeClass(class_isOpen); 73 | data.$body.removeClass( [class_isEnabled, class_isOpen].join(" ") ) 74 | .off(event_clickTouchStart); 75 | 76 | // Navtive MQ Support 77 | if (window.matchMedia !== undefined) { 78 | data.mediaQuery.removeListener(onRespond); 79 | } 80 | 81 | data = {}; 82 | initialized = false; 83 | } 84 | }, 85 | 86 | /** 87 | * @method 88 | * @name disable 89 | * @description Disables navigation system 90 | * @example $.shifter("disable"); 91 | */ 92 | disable: function() { 93 | if (initialized) { 94 | pub.close(); 95 | data.$body.removeClass(class_isEnabled); 96 | } 97 | }, 98 | 99 | /** 100 | * @method 101 | * @name open 102 | * @description Opens navigation if closed 103 | * @example $.shifter("open"); 104 | */ 105 | open: function() { 106 | if (initialized) { 107 | data.$html.addClass(class_isOpen); 108 | data.$body.addClass(class_isOpen); 109 | data.$shifts.one(event_clickTouchStart, onClickTouchStart); 110 | } 111 | } 112 | }; 113 | 114 | /** 115 | * @method private 116 | * @name init 117 | * @description Initializes plugin 118 | * @param opts [object] "Initialization options" 119 | */ 120 | function init(opts) { 121 | if (!initialized) { 122 | data = $.extend({}, options, opts || {}); 123 | 124 | data.$html = $("html"); 125 | data.$body = $("body"); 126 | data.$shifts = $( [classify(class_page), classify(class_header)].join(", ") ); 127 | data.$nav = $( classify(class_navigation) ); 128 | 129 | if (data.$shifts.length > 0 && data.$nav.length > 0) { 130 | initialized = true; 131 | 132 | data.$body.on(event_clickTouchStart, classify(class_handle), onClickTouchStart); 133 | 134 | // Navtive MQ Support 135 | if (window.matchMedia !== undefined) { 136 | data.mediaQuery = window.matchMedia("(max-width:" + (data.maxWidth === Infinity ? "100000px" : data.maxWidth) + ")"); 137 | data.mediaQuery.addListener(onRespond); 138 | onRespond(); 139 | } 140 | } 141 | } 142 | } 143 | 144 | /** 145 | * @method private 146 | * @name onRespond 147 | * @description Handles media query match change 148 | */ 149 | function onRespond() { 150 | if (data.mediaQuery.matches) { 151 | pub.enable(); 152 | } else { 153 | pub.disable(); 154 | } 155 | } 156 | 157 | /** 158 | * @method private 159 | * @name onClickTouchStart 160 | * @description Determines proper click / touch action 161 | * @param e [object] "Event data" 162 | */ 163 | function onClickTouchStart(e) { 164 | e.preventDefault(); 165 | e.stopPropagation(); 166 | 167 | if (!hasTouched) { 168 | if (data.$body.hasClass(class_isOpen)) { 169 | pub.close(); 170 | } else { 171 | pub.open(); 172 | } 173 | } 174 | 175 | if (e.type === "touchstart") { 176 | hasTouched = true; 177 | 178 | setTimeout(resetTouch, 500); 179 | } 180 | } 181 | 182 | /** 183 | * @method private 184 | * @name resetTouch 185 | * @description Resets touch state 186 | */ 187 | function resetTouch() { 188 | hasTouched = false; 189 | } 190 | 191 | /** 192 | * @method private 193 | * @name classify 194 | * @description Create class selector from text 195 | * @param text [string] "Text to convert" 196 | * @return [string] "New class name" 197 | */ 198 | function classify(text) { 199 | return "." + text; 200 | } 201 | 202 | $[namespace] = function(method) { 203 | if (pub[method]) { 204 | return pub[method].apply(this, Array.prototype.slice.call(arguments, 1)); 205 | } else if (typeof method === 'object' || !method) { 206 | return init.apply(this, arguments); 207 | } 208 | return this; 209 | }; 210 | })(jQuery, window); -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | 3 | // Less 4 | 5 | module.exports = function(grunt) { 6 | 7 | grunt.initConfig({ 8 | pkg: grunt.file.readJSON('package.json'), 9 | meta: { 10 | banner: '/* \n' + 11 | ' * <%= pkg.name %> v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> \n' + 12 | ' * <%= pkg.description %> \n' + 13 | ' * <%= pkg.homepage %> \n' + 14 | ' * \n' + 15 | ' * Copyright <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>; <%= pkg.license %> Licensed \n' + 16 | ' */\n' 17 | }, 18 | // JS Hint 19 | jshint: { 20 | options: { 21 | globals: { 22 | 'jQuery': true, 23 | '$' : true 24 | }, 25 | browser: true, 26 | curly: true, 27 | eqeqeq: true, 28 | forin: true, 29 | freeze: true, 30 | immed: true, 31 | latedef: true, 32 | newcap: true, 33 | noarg: true, 34 | nonew: true, 35 | smarttabs: true, 36 | sub: true, 37 | undef: true, 38 | validthis: true 39 | }, 40 | files: [ 41 | 'src/<%= pkg.codename %>.js' 42 | ] 43 | }, 44 | // Copy 45 | copy: { 46 | main: { 47 | files: [ 48 | { 49 | src: 'src/<%= pkg.codename %>.js', 50 | dest: '<%= pkg.codename %>.js' 51 | } 52 | ] 53 | } 54 | }, 55 | // Uglify 56 | uglify: { 57 | options: { 58 | report: 'min' 59 | }, 60 | target: { 61 | files: { 62 | '<%= pkg.codename %>.min.js': [ '<%= pkg.codename %>.js' ] 63 | } 64 | } 65 | }, 66 | // jQuery Manifest 67 | jquerymanifest: { 68 | options: { 69 | source: grunt.file.readJSON('package.json'), 70 | overrides: { 71 | name: '<%= pkg.id %>', 72 | keywords: '<%= pkg.keywords %>', 73 | homepage: '<%= pkg.homepage %>', 74 | docs: '<%= pkg.homepage %>', 75 | demo: '<%= pkg.homepage %>', 76 | download: '<%= pkg.repository.url %>', 77 | bugs: '<%= pkg.repository.url %>/issues', 78 | dependencies: { 79 | jquery: '>=1.7' 80 | } 81 | } 82 | } 83 | }, 84 | // LESS 85 | less: { 86 | main: { 87 | files: { 88 | '<%= pkg.codename %>.css': [ 89 | 'src/<%= pkg.codename %>-config.less', 90 | 'src/<%= pkg.codename %>.less' 91 | ] 92 | } 93 | }, 94 | min: { 95 | options: { 96 | report: 'min', 97 | cleancss: true 98 | }, 99 | files: { 100 | '<%= pkg.codename %>.min.css': [ 101 | 'src/<%= pkg.codename %>-config.less', 102 | 'src/<%= pkg.codename %>.less' 103 | ] 104 | } 105 | } 106 | }, 107 | // Auto Prefixer 108 | autoprefixer: { 109 | options: { 110 | browsers: [ '> 1%', 'last 5 versions', 'Firefox ESR', 'Opera 12.1', 'IE 8', 'IE 9' ] 111 | }, 112 | no_dest: { 113 | src: '*.css' 114 | } 115 | }, 116 | // Banner 117 | usebanner: { 118 | options: { 119 | position: 'top', 120 | banner: '<%= meta.banner %>' 121 | }, 122 | files: { 123 | src: [ 124 | '<%= pkg.codename %>.css', 125 | '<%= pkg.codename %>.js', 126 | '<%= pkg.codename %>.min.css', 127 | '<%= pkg.codename %>.min.js' 128 | ] 129 | } 130 | }, 131 | // Bower sync 132 | sync: { 133 | all: { 134 | options: { 135 | sync: [ 'name', 'version', 'description', 'author', 'license', 'homepage' ], 136 | overrides: { 137 | main: [ 138 | '<%= pkg.codename %>.js', 139 | '<%= pkg.codename %>.css' 140 | ], 141 | ignore: [ "*.jquery.json" ] 142 | } 143 | } 144 | } 145 | }, 146 | // Watcher - For dev only!! 147 | watch: { 148 | scripts: { 149 | files: [ 150 | 'src/**.js' 151 | ], 152 | tasks: [ 153 | 'jshint', 154 | 'copy' 155 | ] 156 | }, 157 | styles: { 158 | files: [ 159 | 'src/**.less' 160 | ], 161 | tasks: [ 162 | 'less', 163 | 'autoprefixer' 164 | ] 165 | } 166 | } 167 | }); 168 | 169 | // Load tasks 170 | grunt.loadNpmTasks('grunt-contrib-watch'); 171 | grunt.loadNpmTasks('grunt-contrib-jshint'); 172 | grunt.loadNpmTasks('grunt-contrib-copy'); 173 | grunt.loadNpmTasks('grunt-contrib-uglify'); 174 | grunt.loadNpmTasks('grunt-jquerymanifest'); 175 | grunt.loadNpmTasks('grunt-contrib-less'); 176 | grunt.loadNpmTasks('grunt-autoprefixer'); 177 | grunt.loadNpmTasks('grunt-banner'); 178 | grunt.loadNpmTasks('grunt-npm2bower-sync'); 179 | 180 | // Readme 181 | grunt.registerTask('buildReadme', 'Build Formstone README.md file.', function () { 182 | var pkg = grunt.file.readJSON('package.json'), 183 | extra = grunt.file.exists('src/README.md') ? '\n\n---\n\n' + grunt.file.read('src/README.md') : ''; 184 | destination = "README.md", 185 | markdown = '

Development of this plugin has ended. Please upgrade to the new Formstone.


\n\n' + 186 | 'Built with Grunt \n' + 187 | '# ' + pkg.name + ' \n\n' + 188 | pkg.description + ' \n\n' + 189 | '- [Demo](' + pkg.demo + ') \n' + 190 | '- [Documentation](' + pkg.homepage + ') \n\n' + 191 | '#### Bower Support \n' + 192 | '`bower install ' + pkg.name + '` ' + 193 | extra; 194 | 195 | grunt.file.write(destination, markdown); 196 | grunt.log.writeln('File "' + destination + '" created.'); 197 | }); 198 | 199 | grunt.registerTask('buildLicense', 'Build Formstone LICENSE.md file.', function () { 200 | var pkg = grunt.file.readJSON('package.json'), 201 | destination = "LICENSE.md", 202 | markdown = 'The MIT License (MIT) \n\n' + 203 | 'Copyright ' + grunt.template.today("yyyy") + ' ' + pkg.author.name + ' \n\n' + 204 | 'Permission is hereby granted, free of charge, to any person obtaining a copy \n' + 205 | 'of this software and associated documentation files (the "Software"), to deal \n' + 206 | 'in the Software without restriction, including without limitation the rights \n' + 207 | 'to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \n' + 208 | 'copies of the Software, and to permit persons to whom the Software is \n' + 209 | 'furnished to do so, subject to the following conditions: \n\n' + 210 | 'The above copyright notice and this permission notice shall be included in \n' + 211 | 'all copies or substantial portions of the Software. \n\n' + 212 | 'THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \n' + 213 | 'IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \n' + 214 | 'FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \n' + 215 | 'AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \n' + 216 | 'LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, \n' + 217 | 'OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN \n' + 218 | 'THE SOFTWARE.'; 219 | 220 | grunt.file.write(destination, markdown); 221 | grunt.log.writeln('File "' + destination + '" created.'); 222 | }); 223 | 224 | // Default task. 225 | grunt.registerTask('default', [ 'jshint', 'copy', 'uglify', 'jquerymanifest', 'less', 'autoprefixer', 'usebanner', 'sync', 'buildReadme', 'buildLicense' ]); 226 | 227 | }; --------------------------------------------------------------------------------