1var makeshift = makeshift || {}; 2makeshift.getParallaxBuilder = function($, global){ 3 4 // some hasty fixes for ff 5 var ua = {}; 6 ua.firefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; 7 //ua.firefox = false; 8 9 var log = function(msg){ 10 $('#log').html(msg); 11 }; 12 13 /** 14 * The handler behind the parallax handlings 15 * 16 * @memberof! makeshift 17 * @namespace makeshift.parallax 18 */ 19 var parallax = { 20 21 /** 22 * Create an instance of `makeshift.parallax` 23 * 24 * @memberof! makeshift.parallax 25 */ 26 create: function(parallaxData, site){ 27 return this.prep.apply(Object.create(this), arguments); 28 }, 29 30 /** 31 * Internal preping of a `makeshift.parallax` instance 32 * 33 * @memberof! makeshift 34 * @private 35 */ 36 prep: function(parallaxData, site){ 37 this.site = site; 38 this.data = parallaxData; 39 this.location = { 40 x: this.point.create(0, undefined, 0), 41 y: this.point.create(0), 42 z: this.point.create(0) 43 }; 44 this.shifting = null; 45 this.workings = {}; 46 this.ui = { 47 container: jQuery('<div class="parallax use-cssTransitions" />'), 48 nav: jQuery('' + 49 '<a href="#pm700" class="pagemark-previous">' + 50 '<i class="fa fa-chevron-left"></i>' + 51 '<i class="fa fa-chevron-left"></i>' + 52 '</a>' + 53 '<a href="#pm100" class="pagemark-next">' + 54 '<i class="fa fa-chevron-right"></i>' + 55 '<i class="fa fa-chevron-right"></i>' + 56 '</a>' + 57 '') 58 }; 59 // prevent unwanted scrolling 60 this.ui.container.add('body').add(document.documentElement).scroll(function(e){ 61 e.target.scrollLeft = e.target.scrollTop = 0; 62 }); 63 this.layers = this.layers.create({ 64 ui: this.ui 65 }); 66 this.tick = this.tick.bind(this); /// @TODO: polyfill bind 67 // if we are in justTouch mode i.e. mobile, enable JS transitions always 68 this.justTouch = 'ontouchstart' in window || navigator.msMaxTouchPoints; 69 this.promise = this.loadResources() 70 .then(this.bind(this.processResources)) 71 .then(this.bind(this.maximizeLayers)) 72 .then(this.bind(this.createPagemarks)) 73 .then(this.bind(this.setupEvents)) 74 .then(this.bind(this.setupTouchDrag)) 75 ; 76 return this; 77 }, 78 79 /** 80 * Turn on CSS Transitions (on by default) 81 * 82 * @memberof! makeshift.parallax 83 */ 84 useCSSTransitions: function(){ 85 this.ui.container 86 .removeClass('use-cssTransitions use-jsTransitions') 87 .addClass('use-cssTransitions') 88 ; 89 return this; 90 }, 91 92 /** 93 * Turn on JS Transitions (currently disabled) 94 * 95 * @memberof! makeshift.parallax 96 */ 97 useJSTransitions: function(){ 98 this.ui.container 99 .removeClass('use-cssTransitions use-jsTransitions') 100 .addClass('use-jsTransitions') 101 ; 102 return this; 103 }, 104 105 /** 106 * Step each layer, and identify the content based on page name 107 * at this point the page content should have been already loaded 108 * into contentData, keyed by name. 109 * 110 * @memberof! makeshift.parallax 111 */ 112 assignContent: function(contentData){ 113 if ( !contentData ) { 114 throw new Error('No content data supplied'); 115 } 116 this.layers.pm = this.pagemarker.create(); 117 this.layers.each(this.assignContentPerLayer(contentData), this); 118 }, 119 120 /** 121 * Find + add the content on a per layer basis 122 * 123 * @memberof! makeshift.parallax 124 */ 125 assignContentPerLayer: function(contentData){ 126 return function(n, layer){ 127 var i, l, pages, page, pageData, pm = this.layers.pm, pmBase; 128 if ( !layer || !layer.config.content ) { return; } 129 if ( !(pages=layer.config.content.pages) ) { return; } 130 if ( !(pmBase=layer.config.content['pagemark-base']) ) { 131 throw new Error('please define a pagemark-base'); 132 } 133 for ( i=0, l=pages.length; i<l; i++ ) { 134 if ( !(page=pages[i]) ) { continue; } 135 if ( !(pageData=contentData[page]) ) { continue; } 136 pm.setFromValue('pm' + i + pmBase, true); 137 this.assignContentItem({ 138 x: i, 139 page: page, 140 pageData: pageData, 141 pm: pm, 142 layer: layer 143 }); 144 } 145 }; 146 }, 147 148 /** 149 * For each item of content, we do a bit of processing. This involves 150 * assigning markup, styles and scripts that are associated to the content 151 * 152 * @memberof! makeshift.parallax 153 */ 154 assignContentItem: function(content){ 155 var pm = content.pm; 156 content.display = content.pageData.displays['default']; 157 // calculate the surrounding pagemarks @TODO this could be improved in the next system 158 content.marks = { 159 pmc: pm.get(), 160 pma: pm.getAbove(), 161 pmb: pm.getBelow(), 162 pmr: pm.getRight(), 163 pml: pm.getLeft(), 164 pmi: pm.getIn(), 165 pmo: pm.getOut() 166 }; 167 // assign the triforce 168 this.assignContentMarkup(content); 169 this.assignContentStyles(content); 170 this.assignContentScript(content); 171 }, 172 173 /** 174 * For each content found, add the markup 175 * 176 * @memberof! makeshift.parallax 177 */ 178 assignContentMarkup: function(content){ 179 content.layer.ui.inner.append( 180 '<div class="pagemark pagemark-' + content.x + ' '+ content.marks.pmc + '">' + 181 content.display.getMarkup() + 182 '</div>' 183 ); 184 }, 185 186 /** 187 * For each content found, add the scripts 188 * 189 * @TODO: support for other event types, but that will probably 190 * only occur after a full rewrite of the system ;) 191 * @TODO: refactor the access to the global site var 192 * 193 * @memberof! makeshift.parallax 194 */ 195 assignContentScript: function(content){ 196 var self = this; 197 var scripts = content.display.getScript(); 198 if ( scripts['pocket/focus'] ) { 199 this.assignContentScriptEvent(content, scripts['pocket/focus'], function(content){ 200 return self.pagemark.is(content.marks.pmc); 201 }); 202 } 203 if ( scripts['pocket/blur'] ) { 204 this.assignContentScriptEvent(content, scripts['pocket/blur'], function(content){ 205 return self.pagemark.getLast() == content.marks.pmc; 206 }); 207 } 208 }, 209 210 /** 211 * 212 */ 213 assignContentScriptEvent: function(content, script, validation){ 214 var site = this.site; 215 var event = { 216 method: function(){ 217 // if we aren't the content's pagemark, do nothing 218 if ( !validation(content) ) { 219 return; 220 } 221 raw(); 222 }, 223 off: function(){ 224 site.off('pagemarkchange', event.method); 225 }, 226 on: function(){ 227 site.on('pagemarkchange', event.method); 228 } 229 }; 230 var raw; eval('raw = (function(){' + 231 script.content + 232 '});'); 233 event.on(); 234 }, 235 236 /** 237 * For each content found, add the styles 238 * 239 * @memberof! makeshift.parallax 240 */ 241 assignContentStyles: function(content){ 242 var marks = content.marks; 243 // @TODO: this needs to be refactored, perhaps introduce twig as templating for the CSS. 244 styles = content.display.getStyle(marks.pmc, { 245 local: '.' + marks.pmc, 246 above: '.' + marks.pma, 247 below: '.' + marks.pmb, 248 right: '.' + marks.pmr, 249 leftt: '.' + marks.pml, 250 "in": '.' + marks.pmi, 251 "out": '.' + marks.pmo, 252 // There are two target systems, one using :target and one using :checked 253 target_right: '.parallax-state:checked ~ #pagemarks #' + marks.pmr + ':target ~ .parallax', 254 target_left: '.parallax-state:checked ~ #pagemarks #' + marks.pml + ':target ~ .parallax', 255 target_below: '.parallax-state:checked ~ #pagemarks #' + marks.pmb + ':target ~ .parallax', 256 target_above: '.parallax-state:checked ~ #pagemarks #' + marks.pma + ':target ~ .parallax', 257 target_in: '.parallax-state:checked ~ #pagemarks #' + marks.pmi + ':target ~ .parallax', 258 target_out: '.parallax-state:checked ~ #pagemarks #' + marks.pmo + ':target ~ .parallax', 259 target: '.parallax-state:checked ~ #pagemarks #' + marks.pmc + ':target ~ .parallax', 260 target_right_radio: '.parallax-state:checked ~ #pagemarks .rnav.' + marks.pmr + ':checked ~ .parallax', 261 target_left_radio: '.parallax-state:checked ~ #pagemarks .rnav.' + marks.pml + ':checked ~ .parallax', 262 target_below_radio: '.parallax-state:checked ~ #pagemarks .rnav.' + marks.pmb + ':checked ~ .parallax', 263 target_above_radio: '.parallax-state:checked ~ #pagemarks .rnav.' + marks.pma + ':checked ~ .parallax', 264 target_in_radio: '.parallax-state:checked ~ #pagemarks .rnav.' + marks.pmi + ':checked ~ .parallax', 265 target_out_radio: '.parallax-state:checked ~ #pagemarks .rnav.' + marks.pmo + ':checked ~ .parallax', 266 target_radio: '.parallax-state:checked ~ #pagemarks .rnav.' + marks.pmc + ':checked ~ .parallax', 267 // breakpoints 268 breakpoints_mobile: '@media screen and (max-width: 700px)', 269 breakpoints_mobile_vert: '@media screen and (max-width: 700px) and (orientation: portrait)', 270 breakpoints_tablet: '@media screen and (min-width: 701px) and (max-width: 1000px)' 271 }); 272 styles && content.layer.ui.inner.append(styles); 273 }, 274 275 /** 276 * 277 */ 278 maximizeLayers: function(){ 279 var layers = this.layers.getAll(), self = this; 280 this.workings.layerHeights = jQuery.map(layers, function(layer, i){ 281 return layer.ui.image ? Number(layer.ui.image[0].height) : 0; 282 }); 283 this.workings.layerMaxHeight = Math.max.apply(Math, this.workings.layerHeights); 284 //jQuery.each(layers, function(i, layer){ 285 //layer.ui.outer.css('height', self.workings.layerMaxHeight + 'px'); 286 //}); 287 return this; 288 }, 289 290 /** 291 * Pagemarks are navigable targets, targetted by a `#fragment`. 292 * 293 * Pagemarks are used to divide up each content parallax layer, at predictable offsets. 294 * This allows the user to navigate step by step horizontally across the parallax effect. 295 * 296 * @memberof! makeshift.parallax 297 */ 298 createPagemarks: function(){ 299 console.log('createPagemarks'); 300 var d = jQuery.Deferred(); 301 this.pagemarks = this.pagemarks.create(this); 302 this.pagemark = this.pagemarker.create(this); 303 this.pagemark.setFromFragment(); 304 return d.resolve(this).promise(); 305 }, 306 307 /** 308 * A PageMarker is just an object that represents the decoded `#fragment` in terms 309 * of x, y, z coordinates. 310 * 311 * It also exposes methods to navigate these axis. 312 * 313 * @memberof! makeshift.parallax 314 * @namespace makeshift.parallax.pagemarker 315 */ 316 pagemarker: { 317 318 /** 319 * Create an instance of `makeshift.parallax.pagemarker 320 *` 321 * @memberof! makeshift.parallax.pagemarker 322 */ 323 create: function(){ 324 return this.prep.apply(Object.create(this), arguments); 325 }, 326 327 /** 328 * Internal prep of a newly created instance 329 *` 330 * @memberof! makeshift.parallax.pagemarker 331 * @private 332 */ 333 prep: function(parallax){ 334 var self = this; 335 this.history = []; 336 this.hash = ['pm', 0, 0, 0]; 337 if ( parallax ) { 338 this.parallax = parallax; 339 this.win = jQuery(window); 340 this.win.on('hashchange', this.bind(this.hashchange)); 341 } 342 this.handlers = jQuery({}); 343 return this; 344 }, 345 346 /** 347 * Clone an instance of pagemarker into a brand new object 348 *` 349 * @memberof! makeshift.parallax.pagemarker 350 */ 351 doppel: function(){ 352 return this.create().set(this.x(), this.y(), this.z(), true); 353 }, 354 355 /** 356 * Bind a method to the current context of the `.bind()` method 357 * which if called `makeshift.parallax.pagemarker.bind()` will be 358 * `makeshift.parallax.pagemarker` 359 *` 360 * @memberof! makeshift.parallax.pagemarker 361 */ 362 bind: function(method){ 363 return method.bind(this); 364 }, 365 366 on: function(){ 367 this.handlers.on.apply(this.handlers, arguments); 368 return this; 369 }, 370 371 trigger: function(){ 372 this.handlers.trigger.apply(this.handlers, arguments); 373 return this; 374 }, 375 376 is: function(test){ 377 return this.get() === test; 378 }, 379 380 getFromFragment: function(){ 381 return new String(window.location.hash).replace('#', ''); 382 }, 383 384 getLast: function(){ 385 return this.history.length > 1 ? this.history[this.history.length - 2] : null; 386 }, 387 388 get: function(){ 389 return this.hash.join(''); 390 }, 391 392 getLeft: function(){ 393 return this.doppel().x('-1').get(); 394 }, 395 396 getRight: function(){ 397 return this.doppel().x('+1').get(); 398 }, 399 400 getBelow: function(){ 401 return this.doppel().y('+1').get(); 402 }, 403 404 getAbove: function(){ 405 return this.doppel().y('-1').get(); 406 }, 407 408 getIn: function(){ 409 return this.doppel().z('-1').get(); 410 }, 411 412 getOut: function(){ 413 return this.doppel().z('1').get(); 414 }, 415 416 set: function(x, y, z, withoutUpdate){ 417 this.x(x); 418 this.y(y); 419 this.z(z); 420 this.trigger('change', this.get()); 421 !withoutUpdate ? this.update() : this.withoutUpdate(); 422 return this; 423 }, 424 425 setFromValue: function(pm, withoutUpdate){ 426 if ( pm[0] + pm[1] == 'pm' ) { 427 !withoutUpdate && console.log('setting pagemark to', pm); 428 this.set(pm[2], pm[3], pm[4], withoutUpdate); 429 } 430 return this; 431 }, 432 433 setFromFragment: function(withoutUpdate){ 434 this.setFromValue(this.getFromFragment(), withoutUpdate); 435 return this; 436 }, 437 438 str: function(a, b){ 439 if ( b.charAt(0) == '+' ) { 440 a += parseInt(b, 10); 441 } 442 else if ( b.charAt(0) == '-' ) { 443 a += parseInt(b, 10); 444 } 445 else { 446 a = parseInt(b, 10); 447 } 448 return a; 449 }, 450 451 x: function(v){ 452 if ( arguments.length ) { 453 if ( v.charAt ) { v = this.str(this.x(), v); } 454 this.hash[1] = this.limit(v, 0, 7, false); 455 return this; 456 } 457 else { 458 return this.hash[1]; 459 } 460 }, 461 462 y: function(v){ 463 if ( arguments.length ) { 464 if ( v.charAt ) { v = this.str(this.y(), v); } 465 this.hash[2] = this.limit(v, 0, 1, true); 466 return this; 467 } 468 else { 469 return this.hash[2]; 470 } 471 }, 472 473 z: function(v){ 474 if ( arguments.length ) { 475 if ( v.charAt ) { v = this.str(this.z(), v); } 476 this.hash[3] = this.limit(v, 0, 2, false); 477 return this; 478 } 479 else { 480 return this.hash[3]; 481 } 482 }, 483 484 limit: function(v, mi, ma, wrap){ 485 if ( v < mi ) v = wrap ? ma : mi; 486 if ( v > ma ) v = wrap ? mi : ma; 487 return v; 488 }, 489 490 update: function(){ 491 var v = this.get(); 492 this.updateHistory(v); 493 this.parallax && this.parallax.jumpTo(v); 494 return this; 495 }, 496 497 withoutUpdate: function(){ 498 this.updateHistory(this.get()); 499 }, 500 501 updateHistory: function(_v){ 502 var v = _v || this.get(), l = this.history.length; 503 if ( this.history[l-1] ) { 504 if ( this.history[l-1] != v ) { 505 this.history.push(v); 506 } 507 } 508 else { 509 this.history.push(v); 510 } 511 }, 512 513 force: function(pm){ 514 var current = pm || this.get(); 515 this.beingForced = true; 516 window.location.hash = ''; 517 // update the internal tracking inside pagemark, but don't react 518 this.setFromValue(current, true); 519 // then trigger the hash change through the parallxx handler 520 this.parallax && this.parallax.jumpTo(current); 521 }, 522 523 /**
524 * Hashchange can trigger in a number of different ways 525 * - user types manually 526 * - user clicks a link 527 * - js calls this.parallax.jumpTo() 528 * - history state 529 */ 530 hashchange: function(e){ 531 if ( this.beingForced ) { 532 this.beingForced = false; 533 return; 534 } 535 // update the recording of the pagemark without triggering anything 536 this.setFromFragment(true); 537 // @BADHACK: this file shouldn't reference site 538 this.parallax.site.trigger('pagemarkchange', {}); 539 } 540 541 }, 542 543 tabfocusHandler: { 544 545 create: function(){ 546 return this.prep.apply(Object.create(this), arguments); 547 }, 548 549 prep: function(parallax){ 550 this.parallax = parallax; 551 this.doc = jQuery(document); 552 this.doc.on('focusin', this.bind(this.focusIn)); 553 this.doc.on('focusout', this.bind(this.focusOut)); 554 this.doc.on('mousedown touchstart', this.bind(this.nonTabInput)); 555 this.reactTabFocus = this.bind(this.reactTabFocus); 556 this.clearTabFocus = this.bind(this.clearTabFocus); 557 return this; 558 }, 559 560 bind: function(method){ 561 return method.bind(this); 562 }, 563 564 /** 565 * Whenever an element is focused, mark it as being tab focused 566 * however, focus events are triggered by all types of different 567 * interactions, so we have to filter out the ones we don't want. 568 * 569 * notes: 570 * focusin can trigger several times before the delayed reactTabFocus 571 * is triggered. 572 */ 573 focusIn: function(e){ 574 var elm = jQuery(e.target); 575 if ( this.actualTarget && this.actualTarget.is(e.target) ) return; 576 // if the nonTabInput has triggered on a child of the focusable element, the nonTabInput 577 // will trigger before the focusin event. So here we try and detect that. 578 if ( this.nonTabTarget && jQuery.contains(elm.get(0), this.nonTabTarget.get(0)) ) { 579 console.log('non tab input before focus', e.target); 580 elm.each(this.clearTabFocus); 581 } 582 else { 583 console.log('focus in for', e.target); 584 jQuery('.tab-focused').each(this.clearTabFocus); 585 this.lastTarget = elm.addClass('tab-focused'); 586 this.delayed = setTimeout(this.reactTabFocus, 100); 587 } 588 }, 589 590 /** 591 * focusout can be triggered on elements that aren't the current 592 * focus, so we need to handle removing anything set on those 593 * tabbed away elements. 594 */ 595 focusOut: function(e){ 596 jQuery(e.target).each(this.clearTabFocus); 597 }, 598 599 /** 600 * If we get non-tab input on any element or sub-element of a tab-focused 601 * group, then cancel the tab focus reaction. 602 */ 603 nonTabInput: function(e){ 604 console.log('non tab input for', e.target); 605 var elm = jQuery(e.target), target = elm; 606 this.nonTabTarget = target; 607 if ( elm.is('.tab-focused') || (target=elm.closest('.tab-focused')) ) { 608 target.each(this.clearTabFocus); 609 } 610 }, 611 612 /** 613 * Responsible for clearing any left behind tab-focus handling and 614 * cancelling the tab-focus reaction when it isn't needed. 615 */ 616 clearTabFocus: function(i, elm){ 617 console.log('clearing tab-focus for', elm); 618 jQuery(elm).removeClass('tab-focused'); 619 if ( this.lastTarget && (this.lastTarget.get(0) === elm) ) { 620 this.delayed && clearTimeout(this.delayed); 621 this.delayed = null; 622 this.lastTarget = null; 623 } 624 }, 625 626 reactTabFocus: function(){ 627 if ( !this.lastTarget ) return; 628 if ( this.actualTarget && this.lastTarget.is(this.actualTarget.get(0)) ) return; 629 console.log('reacting to focus'); 630 var fre = new RegExp('pm([0-9])([0-9])([0-9])', 'gi'); 631 this.actualTarget = this.lastTarget; 632 if ( this.actualTarget ) { 633 var pm = this.actualTarget.closest('.pagemark'), m, sp; 634 if ( pm.length ) { 635 fre.lastIndex = 0; 636 m = fre.exec(pm.attr('class')); 637 if ( m && m[0] && m[0] !== this.parallax.pagemark.last ) { 638 this.parallax.pagemark.setFromValue(m[0]); 639 // @TODO: need to investigate why a refocus is required 640 // there must be something occuring that is wiping focus 641 // perhaps the hash change?
642 this.actualTarget.focus(); 643 } 644 } 645 } 646 } 647 648 }, 649 650 /** 651 * 652 */ 653 setupTouchDrag: function(){ 654 console.log('setupTouchDrag'); 655 this.touchDrag = this.touchDragController.create(this, { 656 type: this.justTouch ? 'swipe' : 'pan' 657 }); 658 return jQuery.Deferred().resolve(this).promise(); 659 }, 660 661 /** 662 * 663 */ 664 setupEvents: function(){ 665 console.log('setupEvents'); 666 var d = jQuery.Deferred(), container = this.ui.container, self = this, win = jQuery(window), doc = jQuery(document); 667 var fre = new RegExp('pm([0-9])([0-9])([0-9])', 'gi'); 668 669 this.tabfocus = this.tabfocusHandler.create(this); 670 671 /* 672 doc.on('focusin', function(e){ 673 jQuery(e.target).addClass('tab-focused'); 674 if ( self.tabFocused === e.target ) { 675 return; 676 } 677 self.tabFocused = e.target; 678 var pm = $(e.target).closest('.pagemark'), m, se; 679 jQuery('.tab-focused').removeClass('tab-focused'); 680 jQuery(e.target).addClass('tab-focused'); 681 console.log('focused in', e.target); 682 log('focused in ' + jQuery(e.target).elementPath()); 683 if ( pm.length ) { 684 fre.lastIndex = 0; 685 m = fre.exec(pm.attr('class')); 686 if ( m && m[0] && m[0] !== self.pagemark.last ) { 687 self.pagemark.setFromValue(m[0]); 688 se = self.getScrollParent(e.target); 689 if ( se ) { 690 se.scrollTop = 0; 691 se.scrollLeft = 0; 692 } 693 //e.target.focus && e.target.focus(); 694 } 695 } 696 }); 697 698 doc.on('focusout', function(e){ 699 log('focused out ' + jQuery(e.target).elementPath()); 700 jQuery(e.target).removeClass('tab-focused'); 701 }); 702 */ 703 704 /* 705 win 706 // 707 .on('hashchange', function(e){ 708 container.find('.at-rest').removeClass('at-rest'); 709 }) 710 // because transition start doesn't exist, why??! 711 .on('transitionstart', function(e){ 712 jQuery(e.target).removeClass('at-rest'); 713 }) 714 // 715 .on('transitionend', function(e){ 716 jQuery(e.target).addClass('at-rest'); 717 }) 718 ; 719 */ 720 return d.resolve(this).promise(); 721 }, 722 723 /** 724 * 725 */ 726 pagemarks: { 727 728 /** 729 * Create our pagemarks 730 * 731 * Put simply this means generate a list of element wrappers and associated 732 * CSS rules, that allow a change in parallax based on a specific url hash/fragment 733 */ 734 create: function(parallax){ 735 return this.prep.apply(Object.create(this), arguments); 736 }, 737 738 /** 739 * 740 */ 741 prep: function(parallax){ 742 this.data = parallax.data.pagemarks; 743 this.parallax = parallax; 744 this.states = this.data.states; 745 this.mode = 'flat'; 746 this.ui = parallax.ui; 747 this.ui.pagemarks = { 748 targets: jQuery('#pagemarks'), 749 styles: null 750 }; 751 this.build(); 752 return this; 753 }, 754 755 styleListGroup: { 756 757 create: function(){ 758 return this.prep.apply(Object.create(this), arguments); 759 }, 760 761 prep: function(){ 762 this.items = arguments; 763 return this; 764 }, 765 766 add: function(data){ 767 this.each(function(item, i){ 768 item.add(data); 769 }); 770 return this; 771 }, 772 773 set: function(name, value){ 774 this.each(function(item, i){ 775 item.set(name, value); 776 }); 777 return this; 778 }, 779 780 /** 781 * 782 */ 783 n: function(n){ 784 return this.items[n]; 785 }, 786 787 render: function(){ 788 var ret = []; 789 this.each(function(item, i){ 790 ret.push(item.render()); 791 }); 792 return ret.join(""); 793 }, 794 795 each: function(m, context){ 796 for ( var i=0, l=this.items.length; i<l; i++ ) { 797 m.call(context || this.items[i], this.items[i], i); 798 } 799 return this; 800 } 801 802 }, 803 804 styleList: { 805 806 create: function(){ 807 return this.prep.apply(Object.create(this), arguments); 808 }, 809 810 /** 811 * 812 */ 813 prep: function(){ 814 this.re = new RegExp('\\$([a-z]+) ?', 'gi'); 815 this.i = {}; 816 this.i.values = {}; 817 this.i.list = []; 818 return this; 819 }, 820 821 /** 822 * 823 */ 824 instance: function(){ 825 var inst = this.create(); 826 inst.i = Object.create(this.i); 827 inst.i.values = Object.create(this.i.values); 828 inst.i.list = this.i.list.slice(0); 829 inst.i.template = this.i.template; 830 inst.i.varmap = this.i.varmap; 831 return inst; 832 }, 833 834 /** 835 * 836 */ 837 set: function(name, value){ 838 this.i.values[name] = value; 839 return this; 840 }, 841 842 /** 843 * 844 */ 845 get: function(name){ 846 return this.i.values[name]; 847 }, 848 849 /** 850 * 851 */ 852 n: function(){ 853 return this; 854 }, 855 856 /** 857 * 858 */ 859 add: function(data){ 860 var str = this.i.template; 861 for ( var k in this.i.varmap ) { 862 if ( data[k] !== undefined ) { 863 str = str.replace('$' + k, data[k]); 864 } 865 else if ( this.i.values[k] !== undefined ) { 866 str = str.replace('$' + k, this.i.values[k]); 867 } 868 } 869 this.i.list.push(str); 870 return this; 871 }, 872 873 /** 874 * 875 */ 876 template: function(template){ 877 var m, i = 0; 878 if ( arguments.length ) { 879 this.i.template = template; 880 this.i.varmap = {}; 881 this.re.lastIndex = 0; 882 while ( (m=this.re.exec(template)) ) { 883 this.i.varmap[m[1]] = i++; 884 } 885 return this; 886 } 887 else { 888 return this.i.template; 889 } 890 }, 891 892 /** 893 * 894 */ 895 renderer: function(){ 896 if ( arguments.length ) { 897 this.i.renderer = arguments[0]; 898 return this; 899 } 900 else { 901 return this.i.renderer; 902 } 903 }, 904 905 /** 906 * 907 */ 908 render: function(){ 909 if ( this.i.renderer ) { 910 return this.i.renderer.apply(this, arguments); 911 } 912 else { 913 return jQuery.trim(this.i.list.join("\r\n")) + "\r\n"; 914 } 915 } 916 917 }, 918 919 splitPID: function(pid){ 920 return 'x' + pid.charAt(0)+' y' + pid.charAt(1) + ' z' + pid.charAt(2); 921 }, 922 923 addNav: function(workspace, pagemark, type, pid, icon, isDefault){ 924 workspace.navs.push( 925 '<a href="#pm' + pagemark.nav[type] + '" class="pagemark-' + type + ' pm-nav with-pm' + pid + ( 926 isDefault ? ' pm-default' : '' 927 ) + '">' + 928 '<i class="fa fa-chevron-' + icon + '"></i>' + 929 '<i class="fa fa-chevron-' + icon + '"></i>' + 930 '</a>' 931 ); 932 }, 933 934 owns: function(obj, key){ 935 return !Object.hasOwnProperty || Object.hasOwnProperty.call(obj, key); 936 }, 937 938 /** 939 * 940 */ 941 build: function(){ 942 var workspace = { 943 navs: [], 944 radios: { 945 items: {}, 946 list: [] 947 }, 948 states: [], 949 head: this.ui.pagemarks.targets, 950 layers: this.parallax.layers.getAll(), 951 hzf: 0 ? ' height: 0 !important;' : '', 952 isDefault: false, 953 styles: { 954 withs: {}, 955 layers: {} 956 } 957 }; 958 workspace.base = this.styleList.create() 959 .template('$state $before $target $after $rules') 960 .set('state', 'state-default') 961 .set('before', '') 962 .set('after', '') 963 .renderer(function(){ 964 return jQuery.trim(this.i.list.join("\r\n").replace(/ +/gi, ' ')) + "\r\n"; 965 }) 966 ; 967 // this stores the styles on a per layer basis 968 workspace.styles.layers.initial = this.styleListGroup.create( 969 workspace.base.instance().set('target', '') 970 ); 971 // this stores the styles for the items that should appear with the targeted pagemark 972 workspace.styles.withs.initial = this.styleListGroup.create( 973 workspace.base.instance().set('target', '') 974 ); 975 workspace.styles.layers.targeted = this.styleListGroup.create( 976 workspace.base.instance().set('target', ''), 977 workspace.base.instance().set('target', '') 978 ); 979 workspace.styles.withs.targeted = this.styleListGroup.create( 980 workspace.base.instance().set('target', ''), 981 workspace.base.instance().set('target', '') 982 ); 983 var order = this.data.order || Object.keys(this.data.items); 984 for ( var pid, i=0, l=order.length; i<l,(pid=order[i]); i++ ) { 985 this.owns(this.data.items, pid) && this.buildPagemark(workspace, pid, this.data.items[pid]); 986 } 987 this.ui.pagemarks.styles = jQuery('<style id="pagemark-styles" />').text( 988 "/**\n"+ 989 " * Withs initial\n"+ 990 " */\n"+ 991 workspace.styles.withs.initial.render() + 992 "/**\n"+ 993 " * Layers initial\n"+ 994 " */\n"+ 995 workspace.styles.layers.initial.render() + 996 "/**\n"+ 997 " * Withs targeted\n"+ 998 " */\n"+ 999 workspace.styles.withs.targeted.render() + 1000 "/**\n"+ 1001 " * Layers targeted\n"+ 1002 " */\n"+ 1003 workspace.styles.layers.targeted.render() + 1004 '').appendTo('head'); 1005 // 1006 //workspace.head = this.ui.pagemarks.targets; 1007 // 1008 workspace.head.addClass('finalmark'); 1009 // 1010 workspace.navs.length && workspace.head.append(workspace.navs.join("\r\n")); 1011 /// add radios for the non-js handling 1012 workspace.radios.list.length && workspace.head.append(workspace.radios.list.join("\r\n")); 1013 var item, state; 1014 // 1015 for ( state in this.states.items ) { 1016 item = this.states.items[state]; 1017 jQuery('body').prepend('<input ' +
1018 'type="radio" id="state-' + state + '" name="ps" value="' + state + '" class="' + item['class'] + '" ' + 1019 ( item['default'] ? 'checked="checked" ' : '' ) + 1020 '/>'); 1021 } 1022 /// add our parallax within the pagemark structure 1023 workspace.head.append(this.ui.container); 1024 this.states.trigger && this.changeState(this.states.trigger); 1025 1026 // move our nav into the pagemarks so we can target based on nav point 1027 jQuery('#menu').appendTo(this.ui.pagemarks.targets); 1028 jQuery('#hud').appendTo(this.ui.pagemarks.targets); 1029 1030 /// 1031 this.ui.pagemarks.finalmark = workspace.head; 1032 this.ui.pagemarks.targets.addClass('rendered'); 1033 }, 1034 1035 /** 1036 * @TODO: work pid to be referenced from the workspace 1037 */ 1038 buildPagemark: function(workspace, pid, pagemark){ 1039 workspace.pid = { value: pid, split: this.splitPID(pid) }; 1040 if ( this.mode === 'flat' ) { 1041 workspace.styles.layers.targeted.n(0).set('target', '#pm' + pid + ':target ~ .parallax'); 1042 workspace.styles.layers.targeted.n(1).set('target', '.rnav.pm' + pid + ':checked ~ .parallax'); 1043 workspace.styles.withs.targeted.n(0).set('target', '#pm' + pid + ':target ~'); 1044 workspace.styles.withs.targeted.n(1).set('target', '.rnav.pm' + pid + ':checked ~'); 1045 // 1046 jQuery('<span id="pm' + pid + '" />') 1047 .addClass('pm') 1048 .addClass(workspace.pid.split) 1049 .appendTo(this.ui.pagemarks.targets) 1050 ; 1051 } 1052 else { 1053 workspace.styles.layers.targeted.n(0).set('target', '#pm' + pid + ':target'); 1054 workspace.styles.layers.targeted.n(1).set('target', '.rnav.pm' + pid + ':checked ~ .parallax'); 1055 workspace.styles.withs.targeted.n(0).set('target', '#pm' + pid + ':target'); 1056 workspace.styles.withs.targeted.n(1).set('target', '.rnav.pm' + pid + ':checked ~ .parallax'); 1057 // nest one pagemark inside the next, creates hideous HTML, but means a functional non-js 1058 // impressively navigational website. 1059 workspace.head = jQuery('<span id="pm' + pid + '" />') 1060 .addClass('pm pm-nested') 1061 .addClass(this.splitPID(pid)) 1062 .appendTo(workspace.head) 1063 ; 1064 } 1065 // step each state and create the required HTML and Styles 1066 if ( !pagemark.states ) { pagemark.states = {}; } 1067 var i, l, state, sel, order = this.data.states.order || Object.keys(pagemark.states); 1068 for ( i=0, l=order.length; i<l,(state=order[i]); i++ ) { 1069 if ( !pagemark.states[state] ) continue; 1070 sel = '#state-' + state + ':checked ~ #pagemarks'; 1071 workspace.styles.withs.initial.set('state', sel); 1072 workspace.styles.layers.initial.set('state', sel); 1073 workspace.styles.withs.targeted.set('state', sel); 1074 workspace.styles.layers.targeted.set('state', sel); 1075 this.buildPagemarkNav(workspace, pid, state, pagemark, !!pagemark['default']); 1076 this.buildPagemarkLayers(workspace, pid, state, pagemark, !!pagemark['default']); 1077 } 1078 }, 1079 1080 /** 1081 * 1082 */ 1083 buildPagemarkNav: function(workspace, pid, state, pagemark, isDefault){ 1084 if ( !workspace.radios.items[pid] ) { 1085 workspace.radios.items[pid] = true; 1086 workspace.radios.list.push('<input type="radio" name="keynav" class="rnav pm' + pid + ' ' + workspace.pid.split + '" autofocus /><span class="rnav-gap"></span>'); 1087 } 1088 if ( pagemark.nav ) { 1089 pagemark.nav.up && this.addNav(workspace, pagemark, 'up', pid, 'up', isDefault); 1090 pagemark.nav.down && this.addNav(workspace, pagemark, 'down', pid, 'down', isDefault); 1091 pagemark.nav.prev && this.addNav(workspace, pagemark, 'prev', pid, 'left', isDefault); 1092 pagemark.nav.next && this.addNav(workspace, pagemark, 'next', pid, 'right', isDefault); 1093 if ( isDefault ) { 1094 // if we are the default page mark, show our with-pms for this default pagemark, by default. 1095 workspace.styles.withs.initial.add({ 1096 after: '.with-pm' + pid, 1097 rules: '{ display: block; }' 1098 }); 1099 } 1100 else { 1101 // if we aren't a default page mark, disable the with-pms for the default pagemark 1102 workspace.styles.withs.targeted.add({ 1103 after: '.pm-default', 1104 rules: '{ display: none; }' 1105 }); 1106 } 1107 // enable any with-pm elements for the targeted page mark. 1108 workspace.styles.withs.targeted.add({ 1109 after: '.with-pm' + pid, 1110 rules: '{ display: block; }' 1111 }); 1112 } 1113 else { 1114 // if there is no nav present for this page mark, just make sure to hide the default 1115 workspace.styles.withs.targeted.add({ 1116 after: '.pm-default', 1117 rules: '{ display: none; }' 1118 }); 1119 } 1120 }, 1121 1122 /** 1123 * 1124 */ 1125 buildPagemarkLayers: function(workspace, pid, state, pagemark, isDefault){
1126 workspace.layers.forEach(function(layer){ 1127 var visible = layer.checkVisibility(pagemark.states[state]); 1128 /// for the first pagemark, use this as the "default" setup 1129 if ( isDefault ) { 1130 workspace.styles.layers.initial.add({ 1131 after: '.layer.' + layer.name, 1132 rules: '{ ' + 1133 'transform: ' + 1134 layer.translateString( 1135 pagemark.states[state].x, 1136 pagemark.states[state].y + 10, 1137 pagemark.states[state].z 1138 ) + 1139 ';' + 1140 (visible ? ' opacity: 1;' : ' opacity: 0;') + 1141 '}' 1142 }); 1143 } 1144 /// then generate styles specific to the pagemark 1145 workspace.styles.layers.targeted.add({ 1146 after: '.layer.' + layer.name, 1147 rules: '{ ' + 1148 'transform: ' + 1149 layer.translateString( 1150 pagemark.states[state].x, 1151 visible ? pagemark.states[state].y : pagemark.states[state].y + 10, 1152 pagemark.states[state].z 1153 ) + 1154 ';' + 1155 (visible ? ' opacity: 1;' : ' opacity: 0;' + workspace.hzf) + 1156 '}' 1157 }); 1158 // if we have a content layer, switch pointer events depending on visibility 1159 if ( !workspace.hzf && layer.isContentLayer() ) { 1160 if ( isDefault ) { 1161 workspace.styles.layers.initial.add({ 1162 after: '.layer.' + layer.name, 1163 rules: '{ ' + (visible ? 'pointer-events: auto;' : 'pointer-events: none;') + ' }' 1164 }); 1165 } 1166 workspace.styles.layers.targeted.add({ 1167 after: '.layer.' + layer.name, 1168 rules: '{ ' + (visible ? 'pointer-events: auto;' : 'pointer-events: none;') + ' }' 1169 }); 1170 } 1171 }); 1172 }, 1173 1174 getState: function(){ 1175 return jQuery('.parallax-state:checked').attr('value'); 1176 }, 1177 1178 toggleState: function(){ 1179 var state = this.getState(), states = arguments; 1180 for ( var i=0, l=states.length-1; i<=l; i++ ) { 1181 if ( states[i] === state ) { 1182 this.changeState(states[i === l ? 0 : i + 1]); 1183 return this; 1184 } 1185 } 1186 return this; 1187 }, 1188 1189 changeState: function(state){ 1190 if ( this.states.items[state] ) { 1191 jQuery('#state-' + state).click(); 1192 } 1193 } 1194 1195 }, 1196 1197 /** 1198 * @static 1199 * 1200 * No cross vendor js developer should leave home without this... 1201 */ 1202 calcVendorPrefix: function(){ 1203 if ( !document.body ) { 1204 console && console.enabled && console.log('prefix() requires the DOM to be ready.'); 1205 return null; 1206 } 1207 if ( !this.calcVendorPrefix.value ) { 1208 if ( document.body ) { 1209 for ( var a = ['Webkit','Moz','O','ms'], i=0, l = a.length; i<l; i++ ) { 1210 if ( document.body.style[a[i]+'AnimationName'] !== undefined ) { 1211 return { js: a[i], css: '-' + a[i].toLowerCase() + '-' }; 1212 } 1213 } 1214 } 1215 return (this.calcVendorPrefix.value = { css:'', js:'' }); 1216 } 1217 else { 1218 return this.calcVendorPrefix.value; 1219 } 1220 }, 1221 1222 /** 1223 * 1224 */ 1225 jumpTo: function(pagemarkID){ 1226 this.useCSSTransitions(); 1227 console.log('jumping', window.location.hash, pagemarkID); 1228 //history.replaceState(undefined, undefined, "#test"); 1229 if ( window.location.hash != '#' + pagemarkID ) { 1230 if ( window.location.hash && window.location.replace ) { 1231 window.location.replace('#' + pagemarkID); 1232 } 1233 else { 1234 window.location.hash = pagemarkID; 1235 } 1236 this.clearLocation(); 1237 return true; 1238 } 1239 this.clearLocation(); 1240 return false; 1241 }, 1242 1243 /** 1244 * 1245 */ 1246 clearLocation: function(){ 1247 this.layers.position(null); 1248 }, 1249 1250 /** 1251 * 1252 */ 1253 updateLocation: function(){ 1254 var loc = this.layers.position(); 1255 this.location.x.set(loc.x); 1256 this.location.y.set(loc.y); 1257 this.location.z.set(loc.z); 1258 }, 1259 1260 /** 1261 * 1262 */ 1263 getLocation: function(){ 1264 return { 1265 x: 0 + this.location.x, 1266 y: 0 + this.location.y, 1267 z: 0 + this.location.z 1268 }; 1269 }, 1270 1271 /** 1272 * 1273 */ 1274 setLocation: function(x, y, z){ 1275 this.location.x.set(x); 1276 this.location.y.set(y); 1277 this.location.z.set(z); 1278 this.layers.position(this.location); 1279 }, 1280 1281 /** 1282 * 1283 */ 1284 shiftTo: function(x, y, z, options){ 1285 if ( this.shifting ) { 1286 1287 } 1288 else { 1289 var shifting = this.shifting = {}; 1290 shifting.promise = new Promise(function(resolve, reject){ 1291 shifting.resolve = resolve; 1292 shifting.reject = reject; 1293 }); 1294 options = options || {}; 1295 this.updateLocation(); 1296 this.useJSTransitions(); 1297 this.location.x.target(x, options.duration); 1298 this.location.y.target(y, options.duration); 1299 this.location.z.target(z, options.duration); 1300 } 1301 return shifting.promise; 1302 }, 1303 1304 /** 1305 * 1306 */ 1307 tick: function(delta, changed){ 1308 if ( this.shifting ) { 1309 this.location.x.tick && this.location.x.tick(delta); 1310 this.location.y.tick && this.location.y.tick(delta); 1311 this.location.z.tick && this.location.z.tick(delta); 1312 if ( this.location.x.changed || this.location.y.changed || this.location.z.changed ) { 1313 this.layers.position(this.location); 1314 } 1315 else if ( !this.location.x.animating && !this.location.y.animating && !this.location.z.animating ) { 1316 var p = this.shifting; 1317 this.shifting = null; 1318 p.resolve(); 1319 } 1320 } 1321 }, 1322 1323 /** 1324 * 1325 */ 1326 touchDragController: { 1327 1328 create: function(){ 1329 return this.prep.apply(Object.create(this), arguments); 1330 }, 1331 1332 prep: function(parallax, options){ 1333 this.options = options || { 1334 type: 'pan' 1335 }; 1336 this.ui = {}; 1337 this.ui.container = parallax.ui.container.get(0); 1338 this.parallax = parallax; 1339 if ( this.options.type == 'swipe' ) { 1340 this.a = this.createAllSwipe(this.ui.container); 1341 } 1342 else { 1343 this.h = this.createHorizontalPan(this.ui.container); 1344 this.v = this.createVerticalPan(this.ui.container); 1345 //Hammer.defaults.cssProps.userSelect = 'auto'; 1346 this.h && this.v && this.h.get('pan').requireFailure(this.v.get('pan')); 1347 } 1348 return this; 1349 }, 1350 1351 bind: function(method){ 1352 return method.bind(this); 1353 }, 1354 1355 createAllSwipe: function(container){ 1356 var aa; 1357 aa = new Hammer.Manager(container); 1358 aa.userdata = {}; 1359 aa.userdata.handler = this.swipeAll(aa); 1360 aa.add(new Hammer.Swipe({ direction: Hammer.DIRECTION_ALL })); 1361 return aa; 1362 }, 1363 1364 createHorizontalPan: function(container){ 1365 var hh; 1366 hh = new Hammer.Manager(container); 1367 hh.userdata = {}; 1368 hh.userdata.handler = this.panHorizontal(hh); 1369 hh.add(new Hammer.Pan({ direction: Hammer.DIRECTION_HORIZONTAL, threshold: 10 })); 1370 return hh; 1371 }, 1372 1373 createVerticalPan: function(container){ 1374 var hh; 1375 hh = new Hammer.Manager(container); 1376 hh.userdata = {}; 1377 hh.userdata.handler = this.panVertical(hh); 1378 hh.add(new Hammer.Pan({ direction: Hammer.DIRECTION_VERTICAL, threshold: 10 })); 1379 return hh; 1380 }, 1381 1382 enable: function(){ 1383 if ( this.options.type == 'swipe' ) { 1384 this.a && this.a.on("swipeleft swiperight swipeup swipedown", this.a.userdata.handler); 1385 } 1386 else { 1387 this.h && this.h.on("panstart panmove panend pancancel", this.h.userdata.handler); 1388 this.v && this.v.on("panstart panmove panend pancancel", this.v.userdata.handler); 1389 } 1390 }, 1391 1392 disable: function(){ 1393 if ( this.options.type == 'swipe' ) { 1394 this.a && this.a.off("swipeleft swiperight swipeup swipedown", this.a.userdata.handler); 1395 } 1396 else { 1397 this.h && this.h.off("panstart panmove panend pancancel", this.h.userdata.handler); 1398 this.v && this.v.off("panstart panmove panend pancancel", this.v.userdata.handler); 1399 } 1400 }, 1401 1402 /** 1403 * It seems, for some reason, Chrome mobile does not like having two 1404 * Hammer managers, one for horizontal, one for vertical (like I was doing) 1405 * but switching to a singular handler that handles them all is fine. 1406 * 1407 * This is only really doable for the swipe actions however, as the pan 1408 * actions need the requireFailure() handling - so have to be separate. 1409 */ 1410 swipeAll: function(aa){ 1411 return this.bind(function(e){ 1412 if ( e.type == 'swipeleft' ) { 1413 this.parallax.pagemark.x('+1').update(); 1414 } 1415 else if ( e.type == 'swiperight' ) { 1416 this.parallax.pagemark.x('-1').update(); 1417 } 1418 else if ( e.type == 'swipeup' ) { 1419 this.parallax.pagemark.z('+1').update(); 1420 } 1421 else if ( e.type == 'swipedown' ) { 1422 this.parallax.pagemark.z('-1').update(); 1423 } 1424 }); 1425 }, 1426 1427 panHorizontal: function(hh){ 1428 return this.bind(function(e){ 1429 //if ( e.pointerType == 'mouse' ) return true; 1430 if ( e.type == 'panstart' ) { 1431 hh.userdata.topmost = this.parallax.layers.getTopmostVisible(); 1432 this.parallax.updateLocation(); 1433 hh.userdata.location = this.parallax.getLocation(); 1434 this.parallax.useJSTransitions(); 1435 } 1436 if ( e.type != 'pancancel' ) { 1437 var xd = e.deltaX / hh.userdata.topmost.config.depth.xmul; 1438 var xp = (1 / 3000) * xd; 1439 this.parallax.setLocation( 1440 0 + hh.userdata.location.x + xd, 1441 0 + hh.userdata.location.y, 1442 0 + hh.userdata.location.z 1443 ); 1444 } 1445 if ( e.type == 'panend' ) { 1446 if ( xp < -0.2 ) { this.parallax.pagemark.x('+1').update(); } 1447 if ( xp > 0.2 ) { this.parallax.pagemark.x('-1').update(); } 1448 this.parallax.useCSSTransitions(); 1449 this.parallax.clearLocation(); 1450 } 1451 }); 1452 }, 1453 1454 panVertical: function(hh){ 1455 return this.bind(function(e){ 1456 //if ( e.pointerType == 'mouse' ) return true; 1457 if ( e.type == 'panstart' ) { 1458 hh.userdata.topmost = this.parallax.layers.getTopmostVisible(); 1459 this.parallax.updateLocation(); 1460 hh.userdata.location = this.parallax.getLocation(); 1461 this.parallax.useJSTransitions(); 1462 } 1463 if ( e.type != 'pancancel' ) { 1464 var yp = (1 / this.parallax.ui.container.height()) * e.deltaY; 1465 //this.parallax.setLocation( 1466 // 0 + hh.userdata.location.x, 1467 // 0 + hh.userdata.location.y + e.deltaY, 1468 // 0 + hh.userdata.location.z 1469 //); 1470 } 1471 if ( e.type == 'panend' ) { 1472 //if ( yp < -0.3 ) { this.parallax.pagemark.y('+1').update(); } 1473 //if ( yp > 0.3 ) { this.parallax.pagemark.y('-1').update(); } 1474 if ( yp < -0.3 ) { this.parallax.pagemark.z('+1').update(); } 1475 if ( yp > 0.3 ) { this.parallax.pagemark.z('-1').update(); } 1476 this.parallax.useCSSTransitions(); 1477 this.parallax.clearLocation(); 1478 } 1479 }); 1480 } 1481 1482 }, 1483 1484 /** 1485 * Point API 1486 */ 1487 point: { 1488 1489 create: function(){ 1490 return this.prep.apply(Object.create(this), arguments); 1491 }, 1492 1493 prep: function(value, min, max){ 1494 this.last = null; 1495 this.value = value || 0; 1496 this.from = value || 0; 1497 this.to = value || 0; 1498 this.animating = false;
1499 this.changed = false; 1500 this.min = min !== undefined ? min : undefined; 1501 this.max = max !== undefined ? max : undefined; 1502 this.limited = (this.min !== undefined || this.max !== undefined); 1503 this.tween = new Tween(); 1504 return this; 1505 }, 1506 1507 limit: function(v){ 1508 if ( this.min !== undefined ) { 1509 if ( v < this.min ){ v = this.min; } 1510 } 1511 if ( this.max !== undefined ) { 1512 if ( v > this.max ){ v = this.max; } 1513 } 1514 return v; 1515 }, 1516 1517 set: function(v){ 1518 if ( this.limited ) { v = this.limit(v); } 1519 this.value = v; 1520 this.from = v; 1521 // @TODO: what happens if we are already tweening? 1522 }, 1523 1524 valueOf: function(){ 1525 return this.value || 0; 1526 }, 1527 1528 target: function(target, duration){ 1529 this.from = this.value || 0; 1530 this.to = target || 0; 1531 this.animating = true; 1532 this.tween.set(this.from, this.to - this.from, duration||0, 'quadInOut', false); 1533 this.tween.reset(); 1534 }, 1535 1536 tick: function(delta){ 1537 this.last = this.value; 1538 if ( this.animating ) { 1539 if ( this.tween.expired() ) { 1540 if ( this.limited ) { 1541 this.value = this.limit(this.to); 1542 } 1543 else { 1544 this.value = this.to; 1545 } 1546 this.animating = false; 1547 } 1548 else { 1549 if ( this.limited ) { 1550 this.value = this.limit(this.tween.getValue()); 1551 } 1552 else { 1553 this.value = this.tween.getValue(); 1554 } 1555 } 1556 } 1557 this.changed = this.value !== this.last; 1558 }, 1559 1560 /** 1561 * @static 1562 * 1563 * Calculate the difference between two points, (taking into account offset). 1564 */ 1565 calcPointDifference: function( a, b, o ){ 1566 var obj = { 1567 x: Math.round(b.x - a.x), 1568 y: Math.round(b.y - a.y), 1569 y: Math.round(b.y - a.z) 1570 }; 1571 if ( o ) { 1572 obj.x -= o.x; 1573 obj.y -= o.y; 1574 obj.z -= o.z; 1575 } 1576 return obj; 1577 }, 1578 1579 /** 1580 * @static 1581 * 1582 * Take a current value, and a "speed", increment the value by speed 1583 * as you decrease the speed by the dec multiplier. When reaching 1584 * zero speed return the value. Allows for predicting where a 1585 * particular easing out velocity will land. 1586 */ 1587 calcContinuedMomentum: function( v, vspeed, dec ){ 1588 dec = Math.max(Math.min(dec||0.9, 0.95),0); 1589 while ( Math.abs(vspeed) > 1 ) { vspeed *= dec; v += vspeed; }; 1590 return v; 1591 }, 1592 1593 /** 1594 * @static 1595 * 1596 * A simple cubic easing function, slows in and out. 1597 * 1598 * t = time 1599 * b = start value 1600 * c = total change 1601 * d = duration 1602 */ 1603 easeInOutCubic: function (t, b, c, d) { 1604 t /= d/2; 1605 if (t < 1) return c/2*t*t*t + b; 1606 t -= 2; 1607 return c/2*(t*t*t + 2) + b; 1608 } 1609 1610 }, 1611 1612 /** 1613 * Layers API 1614 */ 1615 layers: { 1616 1617 create: function(config){ 1618 return this.prep.apply(Object.create(this), arguments); 1619 }, 1620 1621 prep: function(config){ 1622 this.ui = config.ui; 1623 this.list = []; 1624 this.lookup = {}; 1625 return this; 1626 }, 1627 1628 add: function(layer){ 1629 this.list.push(layer); 1630 this.lookup[layer.name] = layer.appendTo(this.ui.container); 1631 return this; 1632 }, 1633 1634 position: function(position){ 1635 var i, a, l, layer; 1636 if ( position ) { 1637 for ( i=0, a=this.list, l=a.length, layer; (i<l) && (layer=a[i]); i++ ) { 1638 layer.transform(0 + position.x, 0 + position.y, 0 + position.z); 1639 } 1640 return this; 1641 } 1642 else if ( position === null ) { 1643 for ( i=0, a=this.list, l=a.length, layer; (i<l) && (layer=a[i]); i++ ) { 1644 layer.clearTransform(); 1645 } 1646 return this; 1647 } 1648 else { 1649 layer=this.list[this.list.length-1]; 1650 a = layer.transform(); 1651 if ( !layer.isVisible() ) { 1652 a.y -= 10; 1653 } 1654 return a; 1655 } 1656 }, 1657 1658 getByName: function(name){ 1659 return this.lookup[name] || false; 1660 }, 1661 1662 getAll: function(){ 1663 return this.list; 1664 }, 1665 1666 getTopmostVisible: function(){ 1667 var i, a, l, layer, visibles = []; 1668 for ( i=0, a=this.list, l=a.length, layer; (i<l) && (layer=a[i]); i++ ) { 1669 layer.isVisible() && visibles.push(layer); 1670 } 1671 if ( visibles.length > 2 ) { 1672 return visibles[visibles.length-2]; 1673 } 1674 else { 1675 return null; 1676 } 1677 }, 1678 1679 each: function(callback, context){ 1680 for ( var i=0, l=this.list.length; i<l; i++ ) { 1681 callback.call(context || this, i, this.list[i]); 1682 } 1683 } 1684 1685 }, 1686 1687 /** 1688 * Layer API 1689 */ 1690 layer: { 1691 1692 /** 1693 * 1694 */ 1695 create: function(name, config, resource){ 1696 return this.prep.apply(Object.create(this), arguments); 1697 }, 1698 1699 /** 1700 * 1701 */ 1702 prep: function(name, config, resource){ 1703 var layer = this, style; 1704 this.ui = {}; 1705 this.name = name; 1706 this.config = config; 1707 this.coords = jQuery.extend({x:0, y:0, z:0}, this.config.coords || {}); 1708 this.resource = resource; 1709 this.ui.outer = jQuery('<div class="layer" />').addClass(name).addClass(config['class']); 1710 this.ui.inner = jQuery('<div class="inner" />').addClass(name); 1711 if ( config.width ) { 1712 this.ui.outer.css('width', config.width + "px"); 1713 this.ui.inner.css('width', config.width + "px"); 1714 } 1715 this.ui.image = resource && resource.data ? jQuery(resource.data) : null; 1716 //if ( name.indexOf('mist') != -1 ) return this; 1717 if ( this.ui.image ) { 1718 // we could do away for ua.firefox because createImageBitmap only exists for it 1719 // @TODO: it looks like firefox doesn't need this any more, at least not on Mac (but perhaps check Windows) 1720 if ( 0 && ua.firefox && resource.texture && window.createImageBitmap ) { 1721 var c = jQuery('<canvas class="parallax-graphic" />'), ctx = c.get(0).getContext('2d'); 1722 if ( config.graphics.repeat != 'no-repeat' ) { 1723 var w = this.ui.outer.width() || 14000;
1723 // @TODO: find a good way to get the at least initial width 1724 c.attr({ 1725 width: w, 1726 height: resource.texture.height 1727 }); 1728 ctx.rect(0, 0, w, resource.texture.height); 1729 ctx.fillStyle = ctx.createPattern(this.ui.image.get(0), config.graphics.repeat); 1730 ctx.fill(); 1731 } 1732 else { 1733 c.attr({ 1734 width: resource.texture.width, 1735 height: resource.texture.height 1736 }); 1737 c.get(0).getContext('2d').drawImage(this.ui.image.get(0), 0, 0); 1738 } 1739 this.ui.inner.append(c); 1740 } 1741 else if ( config.graphics.repeat && config.graphics.repeat != 'no-repeat' ) { 1742 style = { 'background-image': 'url('+ this.ui.image.attr('src') +')' }; 1743 style['background-repeat'] = config.graphics.repeat; 1744 this.ui.inner.addClass('parallax-background').css(style); 1745 } 1746 else { 1747 if ( resource.texture ) { 1748 this.ui.image.attr({ 1749 "class": 'parallax-graphic', 1750 "width": resource.texture.width, 1751 "height": resource.texture.height 1752 }); 1753 } 1754 this.ui.inner.append(this.ui.image); 1755 } 1756 } 1757 if ( layer.name === 'audio' ) { 1758 this.ui.inner.html('<canvas id="audio-canvas" width="800" height="300" />'); 1759 } 1760 return this; 1761 }, 1762 1763 /** 1764 * 1765 */ 1766 appendTo: function(target){ 1767 jQuery(target).append(this.ui.outer); 1768 this.ui.outer.append(this.ui.inner); 1769 return this; 1770 }, 1771 1772 /** 1773 * 1774 */ 1775 translateString: function(x, y, z){ 1776 return 'translate3d(' + 1777 Math.round(this.coords.x + x * this.config.depth.xmul) + 'px, ' + 1778 Math.round(this.coords.y + y) + 'px, ' + 1779 Math.round((-this.coords.z) + z) + 'px)' 1780 ; 1781 }, 1782 1783 /** 1784 * 1785 */ 1786 translateCoordsFromStyleValue: (function(){ 1787 var matrix = /^matrix\((.+?)\)$/i, matrix3D = /^matrix3D\((.+?)\)$/i; 1788 return function(cssTransform){ 1789 var m; 1790 if ( (m=matrix.exec(cssTransform))) { 1791 m = m[1].split(', '); 1792 return { 1793 x: parseFloat(m[4]), 1794 y: parseFloat(m[5]), 1795 z: 0 1796 }; 1797 } 1798 else if ( (m=matrix3D.exec(cssTransform))) { 1799 m = m[1].split(', '); 1800 return { 1801 x: parseFloat(m[12]), 1802 y: parseFloat(m[13]), 1803 z: parseFloat(m[14]) 1804 }; 1805 } 1806 //console.log( DOMMatrix.setMatrixValue(cssTransform) ); 1807 //console.log( CSSMatrix ); 1808 return null; 1809 }; 1810 })(), 1811 1812 isContentLayer: function(){ 1813 return !!this.config.content; 1814 }, 1815 1816 isVisible: function(){ 1817 return this.ui.outer.css('opacity') != 0; 1818 }, 1819 1820 /** 1821 * @TODO: 1822 */ 1823 checkVisibility: function(viewpoint){ 1824 var dv = this.config.depth.visibility; 1825 var yv = this.config.y.visibility; 1826 if ( yv === false ) { 1827 if ( viewpoint.y < -500 ) { 1828 return false; 1829 } 1830 } 1831 if ( dv === true ) { 1832 return true; 1833 } 1834 else if ( dv && dv.length ) { 1835 return ( viewpoint.z > (this.coords.z + dv[0]) && viewpoint.z < (this.coords.z + dv[1]) ); 1836 } 1837 return false; 1838 }, 1839 1840 /** 1841 * 1842 */ 1843 clearTransform: function(){ 1844 this.ui.outer.css('transform', ''); 1845 }, 1846 1847 /** 1848 * 1849 */ 1850 transform: function(x, y, z){ 1851 if ( arguments.length === 0 ) { 1852 return this.translateCoordsFromStyleValue(this.ui.outer.css('transform')); 1853 } 1854 else { 1855 this.ui.outer.css('transform', this.translateString(x, y, z)); 1856 return this; 1857 } 1858 }, 1859 1860 show: function(){ 1861 this.ui.outer.fadeIn(1500); 1862 }, 1863 1864 hide: function(){ 1865 this.ui.outer.fadeOut(1500); 1866 } 1867 1868 }, 1869 1870 /** 1871 * Promise API 1872 */ 1873 progress: function(){ 1874 return this.promise.progress.apply(this.promise, arguments); 1875 }, 1876 then: function(){ 1877 return this.promise.then.apply(this.promise, arguments); 1878 }, 1879 1880 /** 1881 * utility API 1882 */ 1883 each: function(array, callback){ 1884 for ( var i=0, l=array.length; i<l; i++ ) { 1885 callback.call(this, i, array[i]); 1886 } 1887 }, 1888 1889 bind: function(method){ 1890 return method.bind(this); 1891 }, 1892 1893 /** 1894 * Element API 1895 */ 1896 appendTo: function(target){ 1897 //this.ui.pagemarks.finalmark.append(this.ui.nav); 1898 /// add the pagemark targets to the target element 1899 jQuery(target).append(this.ui.pagemarks.targets); 1900 return this; 1901 }, 1902 1903 /** 1904 * Resource Request API 1905 */ 1906 processResources: function(resources){ 1907 this.each(this.data.layers, function(i, layer){ 1908 this.layers.add(this.layer.create(layer.name, layer, resources[layer.name] || null)); 1909 }); 1910 return this; 1911 }, 1912 1913 resetRequest: function(){ 1914 this.request = []; 1915 return this; 1916 }, 1917 1918 buildRequest: function(){
1919 this.resetRequest(); 1920 return jQuery.eachResolve(this.data.layers, this.extendRequest, this).then(function(){ 1921 return this.request; 1922 }); 1923 }, 1924 1925 extendRequest: function(i, item){ 1926 if ( item && item.graphics ) { 1927 this.request.push({ 1928 name: item.name, 1929 url: item.graphics.src 1930 }); 1931 } 1932 return this; 1933 }, 1934 1935 loadResources: function(){ 1936 var d = jQuery.Deferred(), self = this; 1937 this.buildRequest().then(function(request){ 1938 PIXI.loader 1939 .add(request) 1940 .on('progress', function(state, resource){ 1941 d.notifyWith(self, [Math.round(state.progress)/100, resource]); 1942 }) 1943 .load(function (loader, resources) { 1944 d.resolve(resources); 1945 }) 1946 ; 1947 }); 1948 return d.promise(); 1949 } 1950 1951 }; 1952 1953 return parallax; 1954 1955}; 1956 1957/** 1958 * Used for debugging, generates a tracable "css selector" from an element back up to the root node. 1959 * 1960 * @memberof! jQuery 1961 */ 1962jQuery.fn.elementPath = function(){ 1963 var ret = []; 1964 this.each(function(i, elm){ 1965 var path = ''; 1966 jQuery(elm).parents().not('div span').each(function(i, elm){ 1967 var id; elm = jQuery(elm); 1968 if ( elm.attr('id') ) { 1969 id = '#' + elm.attr('id'); 1970 } 1971 else if ( elm.attr('class') ) { 1972 id = elm.attr('class').split(' ').join('.'); 1973 } 1974 else { 1975 id = String(elm.get(0).nodeName).toLowerCase(); 1976 } 1977 path = ( id ? id + ' ' : '' ) + path; 1978 }); 1979 ret.push(path); 1980 }); 1981 return ret.join("\n"); 1982}; 1983 1984/** 1985 * Find the nearest scroll parent 1986 * 1987 * @memberof! jQuery 1988 */ 1989jQuery.fn.getScrollParent = function( includeHidden ) { 1990 var position = this.css( "position" ), 1991 excludeStaticParent = position === "absolute", 1992 overflowRegex = includeHidden ? (/(auto|scroll|hidden)/) : (/(auto|scroll)/), 1993 scrollParent = this.parents().filter( function() { 1994 var parent = $( this ); 1995 if ( excludeStaticParent && parent.css( "position" ) === "static" ) { 1996 return false; 1997 } 1998 return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + 1999 parent.css( "overflow-x" ) ); 2000 } ).eq( 0 ); 2001 return position === "fixed" || !scrollParent.length ? 2002 $( this[ 0 ].ownerDocument || document ) : 2003 scrollParent; 2004}; 2005 2006/** 2007 * Reset the nearest scroll parent to 0,0 scroll 2008 * 2009 * @memberof! jQuery 2010 */ 2011jQuery.fn.resetScrollParent = function(){ 2012 this.getScrollParent().each(function(i, element){ 2013 element.scrollTop = 0; 2014 element.scrollLeft = 0; 2015 console.log('resetting scroll parent', element); 2016 }); 2017 return this; 2018}; 2019 2020/** 2021 * For each element in the collection, set scrollLeft and scrollTop to 0 2022 * 2023 * @memberof! jQuery 2024 */ 2025jQuery.fn.resetScroll = function(){ 2026 this.each(function(i, elm){ 2027 elm.scrollLeft = elm.scrollTop = 0; 2028 }); 2029 return this; 2030};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.