├── .gitignore ├── assets ├── char.psd └── chars.psd ├── demo ├── images │ └── chars.png ├── index.html └── js │ └── jquery │ └── jquery.splitflap.js ├── .gitattributes ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | Desktop.ini 3 | .DS_Store 4 | .idea/ 5 | -------------------------------------------------------------------------------- /assets/char.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zemax/jquery-splitFlap/HEAD/assets/char.psd -------------------------------------------------------------------------------- /assets/chars.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zemax/jquery-splitFlap/HEAD/assets/chars.psd -------------------------------------------------------------------------------- /demo/images/chars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zemax/jquery-splitFlap/HEAD/demo/images/chars.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 zemax 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jquery-splitFlap 2 | ================ 3 | 4 | jQuery module to transform a div text into splitflap display (airport-like). 5 | 6 | [View the Demo →](http://lab.les-mains-dans-le-code.fr/splitflap/) 7 | 8 | How to use 9 | ---------- 10 | 11 | ``` html 12 |
Hello World
13 | ``` 14 | 15 | ``` javascript 16 | $('.my-spliflap').splitFlap(); 17 | ``` 18 | 19 | Options 20 | ------- 21 | 22 | You can pass options to the function 23 | 24 | ``` javascript 25 | // Default : 26 | $('.my-spliflap').splitFlap({ 27 | image: 'images/chars.png', 28 | imageSize: '', 29 | charsMap: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789.,!?#@()+-=', 30 | charWidth: 50, 31 | charHeight: 100, 32 | charSubstitute: ' ', 33 | speed: 3, 34 | speedVariation: 2, 35 | text: '', 36 | textInit: '', 37 | autoplay: true, 38 | onComplete: function(){} 39 | }); 40 | ``` 41 | 42 | ### image 43 | The path to the image used by the splitflap. 44 | 45 | ### imageSize 46 | If used on a non-natural size (ie for HDPI, etc...), the size of the image can be specified here, in CSS "background-position" format. 47 | 48 | You will certainly need to change *charsMap*, *charWidth* and *charHeight* if you change this. 49 | 50 | ### charsMap 51 | The string represented in the image. 52 | 53 | ### charWidth 54 | The width of a character in the image, in pixels. 55 | 56 | ### charHeight 57 | The height of a character in the image, in pixels. 58 | 59 | ### charSubstitute 60 | The character used when the string contains a character not found in the charsMap. 61 | 62 | ### speed 63 | The speed of the rotation, in letter by seconds. 64 | 65 | ### speedVariation 66 | Random speed added to the fixed speed. 67 | 68 | ### text 69 | The destination text. If empty, the content of the element is used. 70 | 71 | ### textInit 72 | The initial string the animation begin with. 73 | 74 | ### autoplay 75 | If set to false, you'll need to start the animation manually (see below). 76 | 77 | ### onComplete 78 | Callback function when the aniamtion is complete. 79 | 80 | Special options 81 | --------------- 82 | 83 | If the string 'splitflap' is passed as options on an already existing Splitflaped div, the internal SplitFlap object is returned. 84 | 85 | With this object, you can start the animation manually by calling the animate() method. 86 | 87 | ``` javascript 88 | // Initialise the animation 89 | $('.my-spliflap').splitFlap({autoplay: false}); 90 | 91 | // Get the animation object and start it manually 92 | $('.my-spliflap').splitFlap('splitflap').animate(); 93 | ``` 94 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Split Flap 6 | 46 | 47 | 48 | 49 |
50 |

Simple Split-Flap

51 | 52 |
Hello World !
53 |
54 | 55 |
56 |

Autoplay disabled

57 | 58 |
It s nice
59 |
60 | 61 |
62 |

JS-set text

63 | 64 |
65 |
66 | 67 |
68 |

Modified size

69 | 70 |
It s a Small World
71 |
72 | 73 | 74 |
75 |

Chained splitflaps

76 | 77 |
78 |
79 | 80 | 81 | 82 | 128 | 129 | Fork me on GitHub 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /demo/js/jquery/jquery.splitflap.js: -------------------------------------------------------------------------------- 1 | /* Split Flap Display for jQuery / Maxime Cousinou */ 2 | (function ($) { 3 | 'use strict'; 4 | 5 | /*************************************************************************** 6 | * Tools 7 | **************************************************************************/ 8 | 9 | // Date.now Polyfill 10 | Date.now = (Date.now || function () { 11 | return (new Date()).getTime(); 12 | }); 13 | 14 | // requestAnimationFrame polyfill by Erik Möller, fixes from Paul Irish and Tino Zijdel 15 | (function () { 16 | var lastTime = 0; 17 | var vendors = [ 'ms', 'moz', 'webkit', 'o' ]; 18 | 19 | for ( var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x ) { 20 | window.requestAnimationFrame = window[ vendors[ x ] + 'RequestAnimationFrame' ]; 21 | window.cancelAnimationFrame = window[ vendors[ x ] + 'CancelAnimationFrame' ] || window[ vendors[ x ] + 'CancelRequestAnimationFrame' ]; 22 | } 23 | 24 | if ( window.requestAnimationFrame === undefined ) { 25 | window.requestAnimationFrame = function (callback, element) { 26 | var currTime = Date.now(), timeToCall = Math.max(0, 16 - ( currTime - lastTime )); 27 | var id = window.setTimeout(function () { 28 | callback(currTime + timeToCall); 29 | }, timeToCall); 30 | lastTime = currTime + timeToCall; 31 | return id; 32 | }; 33 | } 34 | 35 | window.cancelAnimationFrame = window.cancelAnimationFrame || function (id) { 36 | window.clearTimeout(id); 37 | }; 38 | }()); 39 | 40 | // Bind 41 | var bind = function (method, instance) { 42 | return function () { 43 | return method.apply(instance, arguments); 44 | }; 45 | }; 46 | 47 | /*************************************************************************** 48 | * Letter 49 | **************************************************************************/ 50 | 51 | /** 52 | * Letter Constructor 53 | * 54 | * @param settings 55 | * @constructor 56 | */ 57 | function Letter(settings) { 58 | this.settings = settings; 59 | 60 | this.domObject = document.createElement('div'); 61 | $(this.domObject) 62 | .css({ 63 | '-webkit-transform-style': 'preserve-3d', 64 | '-moz-transform-style': 'preserve-3d', 65 | '-ms-transform-style': 'preserve-3d', 66 | 'transform-style': 'preserve-3d' 67 | }) 68 | .addClass("char"); 69 | 70 | this.upperObject = document.createElement('div'); 71 | this.lowerObject = document.createElement('div'); 72 | this.flippingObject = document.createElement('div'); 73 | 74 | var c = { 75 | 'background-image': 'url(' + this.settings.image + ')' 76 | }; 77 | if ( this.settings.imageSize != '' ) { 78 | c[ 'background-size' ] = this.settings.imageSize; 79 | } 80 | 81 | $(this.upperObject) 82 | .width(this.settings.charWidth) 83 | .height(this.settings.charHeight >> 1) 84 | .css(c) 85 | .addClass("upper"); 86 | 87 | $(this.lowerObject) 88 | .width(this.settings.charWidth) 89 | .height(this.settings.charHeight >> 1) 90 | .css(c) 91 | .addClass("lower"); 92 | 93 | $(this.flippingObject) 94 | .width(this.settings.charWidth) 95 | .height(this.settings.charHeight >> 1) 96 | .css($.extend(c, { 97 | 'position': 'absolute', 98 | 'left': 0, 99 | 'top': 0 100 | })) 101 | .hide() 102 | .addClass("flipping"); 103 | 104 | $(this.domObject).append(this.upperObject); 105 | $(this.domObject).append(this.lowerObject); 106 | $(this.domObject).append(this.flippingObject); 107 | } 108 | 109 | var lp = Letter.prototype; 110 | 111 | /** 112 | * Return DOM Object 113 | * 114 | * @returns {HTMLElement|*} 115 | */ 116 | lp.getDOMObject = function () { 117 | return this.domObject; 118 | } 119 | 120 | lp.getCharOffset = function (char) { 121 | return Math.max(0, this.settings.charsMap.indexOf(char)); 122 | } 123 | 124 | /** 125 | * Set Letter character 126 | * @param char 127 | * @param charFrom 128 | * @param ratio 129 | */ 130 | lp.setChar = function (char, charFrom, ratio) { 131 | if ( typeof ratio == 'undefined' ) { 132 | ratio = 1; 133 | } 134 | 135 | var offset = this.getCharOffset(char); 136 | 137 | $(this.upperObject).css({ 138 | 'background-position': '-' + (offset * this.settings.charWidth) + 'px 0px' 139 | }); 140 | 141 | if ( ratio >= 1 ) { 142 | $(this.lowerObject).css({ 143 | 'background-position': '-' + (offset * this.settings.charWidth) + 'px -' + (this.settings.charHeight >> 1) + 'px' 144 | }); 145 | $(this.flippingObject).hide(); 146 | } 147 | else if ( ratio <= 0 ) { 148 | var offsetFrom = this.getCharOffset(charFrom); 149 | $(this.upperObject).css({ 150 | 'background-position': '-' + (offsetFrom * this.settings.charWidth) + 'px 0px' 151 | }); 152 | $(this.lowerObject).css({ 153 | 'background-position': '-' + (offsetFrom * this.settings.charWidth) + 'px -' + (this.settings.charHeight >> 1) + 'px' 154 | }); 155 | $(this.flippingObject).hide(); 156 | } 157 | else { 158 | var offsetFrom = this.getCharOffset(charFrom); 159 | $(this.lowerObject).css({ 160 | 'background-position': '-' + (offsetFrom * this.settings.charWidth) + 'px -' + (this.settings.charHeight >> 1) + 'px' 161 | }); 162 | 163 | var d; 164 | if ( ratio < 0.5 ) { 165 | d = (90 * 2 * ratio); 166 | $(this.flippingObject) 167 | .css({ 168 | 'top': 0, 169 | 'z-index': Math.round(d), 170 | '-webkit-transform': 'rotateX(-' + d + 'deg)', 171 | '-moz-transform': 'rotateX(-' + d + 'deg)', 172 | '-ms-transform': 'rotateX(-' + d + 'deg)', 173 | 'transform': 'rotateX(-' + d + 'deg)', 174 | '-webkit-transform-origin': 'bottom center 0', 175 | '-moz-transform-origin': 'bottom center 0', 176 | '-ms-transform-origin': 'bottom center 0', 177 | 'transform-origin': 'bottom center 0', 178 | 'background-position': '-' + (offsetFrom * this.settings.charWidth) + 'px 0px' 179 | }); 180 | } 181 | else { 182 | d = (90 * 2 * (1 - ratio)); 183 | $(this.flippingObject) 184 | .css({ 185 | 'top': (this.settings.charHeight >> 1) + 'px', 186 | 'z-index': Math.round(d), 187 | '-webkit-transform': 'rotateX(' + d + 'deg)', 188 | '-moz-transform': 'rotateX(' + d + 'deg)', 189 | '-ms-transform': 'rotateX(' + d + 'deg)', 190 | 'transform': 'rotateX(' + d + 'deg)', 191 | '-webkit-transform-origin': 'top center 0', 192 | '-moz-transform-origin': 'top center 0', 193 | '-ms-transform-origin': 'top center 0', 194 | 'transform-origin': 'top center 0', 195 | 'background-position': '-' + (offset * this.settings.charWidth) + 'px -' + (this.settings.charHeight >> 1) + 'px' 196 | }); 197 | } 198 | $(this.flippingObject).show(); 199 | } 200 | } 201 | 202 | /*************************************************************************** 203 | * SplitFlap 204 | **************************************************************************/ 205 | 206 | function SplitFlap(settings) { 207 | this.settings = settings; 208 | 209 | this.domObject = document.createElement('div'); 210 | this.letters = new Array(); 211 | 212 | $(this.domObject).addClass("splitflap"); 213 | } 214 | 215 | var sp = SplitFlap.prototype; 216 | 217 | sp.getDOMObject = function () { 218 | return this.domObject; 219 | } 220 | 221 | sp.build = function (size) { 222 | $(this.domObject) 223 | .css({ 224 | position: 'relative' 225 | }) 226 | .width(size * this.settings.charWidth) 227 | .height(this.settings.charHeight); 228 | 229 | for ( var i = 0; i < size; i++ ) { 230 | var letter = new Letter(this.settings); 231 | 232 | var o = letter.getDOMObject(); 233 | $(o).css({ 234 | position: 'absolute', 235 | left: i * this.settings.charWidth, 236 | top: 0 237 | }); 238 | 239 | $(this.domObject).append(o); 240 | 241 | this.letters[ i ] = letter; 242 | } 243 | } 244 | 245 | sp.setText = function (text, textFrom) { 246 | var animated; 247 | if ( typeof textFrom == 'undefined' ) { 248 | textFrom = text; 249 | animated = false; 250 | } 251 | else { 252 | animated = true; 253 | } 254 | 255 | // Normalize text 256 | while ( textFrom.length < this.letters.length ) { 257 | if ( this.settings.padDir == 'left' ) { 258 | textFrom = textFrom + this.settings.padChar; 259 | } 260 | else { 261 | textFrom = this.settings.padChar + textFrom; 262 | } 263 | } 264 | 265 | // Initialise display 266 | var charsFrom = (new String(textFrom)).split(""); 267 | 268 | for ( var i = 0, l = this.letters.length; i < l; i++ ) { 269 | var letter = this.letters[ i ]; 270 | letter.setChar(charsFrom[ i ]); 271 | } 272 | 273 | // Animation 274 | if ( animated ) { 275 | // Normalize text 276 | while ( text.length < this.letters.length ) { 277 | if ( this.settings.padDir == 'left' ) { 278 | text = text + this.settings.padChar; 279 | } 280 | else { 281 | text = this.settings.padChar + text; 282 | } 283 | } 284 | 285 | var chars = (new String(text)).split(""); 286 | 287 | this.animation = { 288 | letters: new Array() 289 | }; 290 | 291 | for ( var i = 0, l = this.letters.length; i < l; i++ ) { 292 | var al = { 293 | ratio: 0, 294 | speed: this.settings.speed + Math.random() * this.settings.speedVariation, 295 | letters: new Array(charsFrom[ i ]) 296 | }; 297 | 298 | var index = this.letters[ i ].getCharOffset(charsFrom[ i ]); 299 | while ( this.settings.charsMap.charAt(index) != chars[ i ] ) { 300 | index = (index + 1) % this.settings.charsMap.length; 301 | al.letters.push(this.settings.charsMap.charAt(index)); 302 | } 303 | 304 | this.animation.letters[ i ] = al; 305 | } 306 | 307 | if ( this.settings.autoplay ) { 308 | this.animate(); 309 | } 310 | } 311 | } 312 | 313 | sp.animate = function () { 314 | var t = new Date().getTime(); 315 | if ( typeof this.animation.time == 'undefined' ) { 316 | this.animation.time = t; 317 | } 318 | 319 | var dt = 0.001 * (t - this.animation.time); 320 | 321 | var n = 0; 322 | 323 | for ( var i = 0, l = this.animation.letters.length; i < l; i++ ) { 324 | var letter = this.letters[ i ]; 325 | var al = this.animation.letters[ i ]; 326 | 327 | if ( al.letters.length > 1 ) { 328 | al.ratio += al.speed * dt; 329 | if ( (al.ratio > 1) && (al.letters.length > 1) ) { 330 | al.ratio = 0; 331 | al.letters.shift(); 332 | } 333 | } 334 | 335 | if ( al.letters.length > 1 ) { 336 | letter.setChar(al.letters[ 1 ], al.letters[ 0 ], al.ratio); 337 | n++; 338 | } 339 | else { 340 | letter.setChar(al.letters[ 0 ]); 341 | } 342 | } 343 | 344 | this.animation.time = t; 345 | 346 | if ( n > 0 ) { 347 | requestAnimationFrame(bind(this.animate, this)); 348 | } 349 | else { 350 | this.settings.onComplete(this); 351 | } 352 | } 353 | 354 | /*************************************************************************** 355 | * jQuery 356 | **************************************************************************/ 357 | 358 | $.fn.splitFlap = function (options) { 359 | if ( options == 'splitflap' ) { 360 | if ( this.length < 0 ) { 361 | return false; 362 | } 363 | 364 | var o = this.get(0); 365 | if ( typeof o.splitflap == 'undefined' ) { 366 | return false; 367 | } 368 | 369 | return o.splitflap; 370 | } 371 | 372 | var settings = $.extend({ 373 | image: 'images/chars.png', 374 | imageSize: '', 375 | charsMap: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789.,!?#@()+-=', 376 | charWidth: 50, 377 | charHeight: 100, 378 | charSubstitute: ' ', 379 | padDir: 'left', 380 | padChar: ' ', 381 | speed: 3, 382 | speedVariation: 2, 383 | text: '', 384 | textInit: '', 385 | autoplay: true, 386 | onComplete: function () {} 387 | }, options); 388 | 389 | return this.each(function () { 390 | var text = (new String(settings.text)).toUpperCase(); 391 | if ( text == '' ) { 392 | text = (new String($(this).html())).toUpperCase(); 393 | } 394 | 395 | // Verify chars 396 | for ( var i = 0, l = text.length; i < l; i++ ) { 397 | var c = text.charAt(i); 398 | if ( settings.charsMap.indexOf(c) < 0 ) { 399 | text = text.replace(c, settings.charSubstitute); 400 | } 401 | } 402 | 403 | var textInit = settings.textInit.toUpperCase().substr(0, text.length); 404 | // Verify chars 405 | for ( var i = 0, l = textInit.length; i < l; i++ ) { 406 | var c = textInit.charAt(i); 407 | if ( settings.charsMap.indexOf(c) < 0 ) { 408 | textInit = textInit.replace(c, settings.charSubstitute); 409 | } 410 | } 411 | 412 | while ( textInit.length < text.length ) { 413 | textInit = textInit + settings.charsMap.charAt(Math.floor(settings.charsMap.length * Math.random())); 414 | } 415 | 416 | this.splitflap = new SplitFlap(settings); 417 | this.splitflap.build(text.length); 418 | 419 | $(this).empty().append(this.splitflap.getDOMObject()); 420 | 421 | this.splitflap.setText(text, textInit); 422 | }); 423 | }; 424 | })(jQuery); 425 | --------------------------------------------------------------------------------