├── .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 |
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 |
--------------------------------------------------------------------------------