1/** 2 * Makeshift is the name of the system responsible for my site's parallax. 3 * 4 * @namespace makeshift 5 * @author Phil Glanville 6 */ 7var makeshift = makeshift || {}; 8 9/** 10 * @namespace jQuery 11 */ 12var jQuery; 13 14/** 15 * Used to generate the parallax structure in the form of CSS and HTML. 16 * This returns the initial `site` object, which is then extended. 17 * 18 * @memberof! makeshift 19 */ 20makeshift.generateSite = function($, global, parallax, pockets){ 21 22 parallax = makeshift.getParallaxBuilder.apply(makeshift, arguments); 23 pockets = makeshift.getPocketsBuilder.apply(makeshift, arguments); 24 25 /** 26 * 27 */ 28 var site = { 29 30 listeners: {}, 31 32 /** 33 * Create a new instance of `site` 34 * 35 * @memberof! site 36 */ 37 create: function(config){ 38 return this.prep.apply(Object.create(this), arguments); 39 }, 40 41 /** 42 * Preps a new instance of `site`, used directly by {@link site.create} 43 * 44 * @memberof! site 45 * @private 46 */ 47 prep: function(config){ 48 var d = jQuery.Deferred(); 49 this.promise = d.promise(); 50 this.config = config || {}; 51 this.config.frameRate = this.config.frameRate || 40; 52 this.config.stopped = false; 53 this.config.then = 0; 54 this.config.delta = 0; 55 this.config.interval = 1000 / this.config.frameRate; 56 this.config.raf = requestAnimationFrame; 57 this.config.buildCallback = null; 58 this.animate = this.animate.bind(this); /// @TODO: --- bind needs to exist 59 this.keyboard = new window.keypress.Listener(); 60 this.body = jQuery('body'); 61 this.ui = {}; 62 jQuery(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange', this.events.fullscreenchange); 63 if ( this.isRendered() ) { 64 // don't build anything 65 } 66 else { 67 if ( this.config.progress ) { 68 this.ui.progress = progress 69 .create(this.config.progress) 70 .userdata(this) 71 .then(function(site){d.resolve(site);}) 72 ; 73 this.build().progress(this.ui.progress.update.bind(this.ui.progress)); 74 } 75 else { 76 this.build().then(function(){d.resolve(this);}); 77 } 78 } 79 if ( this.config.ready ) { 80 this.promise = this.promise.then(this.config.ready.bind(this)); 81 } 82 return this; 83 }, 84 85 on: function(name, callback){ 86 if ( !this.listeners[name] ) { 87 this.listeners[name] = []; 88 } 89 this.listeners[name].push(callback); 90 }, 91 92 off: function(name, callback){ 93 if ( this.listeners[name] ) { 94 var i = this.listeners[name].indexOf(callback); 95 if ( i != -1 ) { 96 this.listeners[name].splice(i, 1); 97 } 98 } 99 }, 100 101 trigger: function(name, args, context){ 102 if ( this.listeners[name] ) { 103 // operate on a copy of the list, to avoid modification whilst looping 104 for ( var i=0, a=this.listeners[name].slice(0), l=a.length; i<l; i++ ) { 105 a[i].apply(context || this, args); 106 } 107 } 108 }, 109 110 /** 111 * Extend the `site` object with the own items of the passed in obj param. 112 * 113 * @memberof! site 114 * @param {object} obj The object the properties will be copied from 115 */ 116 extend: function(obj){ 117 var i, own = Object.prototype.hasOwnProperty ? Object.prototype.hasOwnProperty : function(){return true;}; 118 for ( i in obj ) { 119 if ( own.call(obj, i) ) { 120 this[i] = obj[i]; 121 } 122 } 123 return this; 124 }, 125 126 /** 127 * A wrapper function for the Keyboard.js register_combo method. 128 * 129 * @memberof! site 130 * @param {string} keys - a string of keys in keyboard.js combo notation 131 * @param {function} callback - the callback to trigger when the combo is typed 132 * @param {mixed} context - the context to use for the callback method i.e. this 133 */ 134 registerSimpleCombo: function(keys, callback, context){ 135 this.keyboard.register_combo({ 136 "keys" : keys, 137 "on_keydown" : callback, 138 "on_keyup" : null, 139 "on_release" : null, 140 "this" : context, 141 "prevent_default" : false, 142 "prevent_repeat" : true, 143 "is_unordered" : false, 144 "is_counting" : false, 145 "is_exclusive" : false, 146 "is_solitary" : false, 147 "is_sequence" : false 148 }); 149 }, 150 151 /** 152 * Determine if pagemarks have already been rendered, or not. 153 * 154 * @memberof! site 155 */ 156 isRendered: function(){ 157 return !!this.body.find('#pagemarks.rendered').length; 158 }, 159 160 /** 161 * Trigger the dynamic build of the site, first step, load the content. 162 * 163 * @memberof! site 164 */ 165 build: function(callback){ 166 this.config.buildCallback = callback; 167 var d = jQuery.Deferred(), instance = this; 168 $.get(this.config.source).done(function(siteData){ 169 var progress = { parallax: 0, content: 0 }; 170 //if ( siteData.intro ) { instance.intro = instance.createIntro(siteData); } 171 site.siteData = siteData; 172 var parallaxComplete = site.createParallax(siteData) 173 .progress(function(p){ progress.parallax = p; d.notify(progress, instance); }) 174 .then(function(parallax){instance.parallax = parallax;}) 175 ; 176 var contentComplete = site.createContent(siteData) 177 .progress(function(p){ progress.content = p; d.notify(progress, instance); }) 178 .then(function(content){instance.content = content;}) 179 ; 180 var when = jQuery.when(parallaxComplete, contentComplete) 181 .then(function(){ instance.start(); d.resolve(instance); }) 182 ; 183 }); 184 return d.promise(); 185 }, 186 187 /** 188 * Promise API 189 */ 190 progress: function(){ 191 return this.promise.progress.apply(this.promise, arguments); 192 }, 193 then: function(){ 194 return this.promise.then.apply(this.promise, arguments); 195 }, 196 197 /** 198 * Tick -- for every beat of animation 199 */ 200 tick: function(callback){ 201 if ( callback && callback.call ) { 202 !this.tick.list && (this.tick.list = []); 203 this.tick.list.push(callback); 204 } 205 else if ( this.tick && this.tick.list && this.tick.list.length ) { 206 for ( var i=0, a=this.tick.list, l=a.length; i<l; i++ ) { 207 this.tick.list[i].apply(this, arguments); 208 } 209 } 210 }, 211 212 /** 213 * Trigger off the intro.js introduction to the site. 214 * 215 * @memberof! site 216 */ 217 intro: function(){ 218 var data = JSON.parse(JSON.stringify(this.siteData)); 219 var i = this.createIntro(data); i.start(); 220 }, 221 222 /**
223 * Special code to get intro.js to work with transformed layers 224 * also had to hack intro.js slightly -- introducing .show() and .hide(); 225 * and changing the way it finds its elements to use getBoundingClientRect() 226 * 227 * @memberof! site 228 */ 229 createIntro: function(siteData){ 230 for ( var i=0, l=siteData.intro.steps.length, item; i<l, (item=siteData.intro.steps[i]); i++ ) { 231 if ( item.element ) { 232 item.element = jQuery(item.element).get(0); 233 } 234 } 235 var site = this, intro = introJs().setOptions(siteData.intro); 236 intro.onbeforechange(function(nextElm) { 237 if ( site.body.isNot('.introducing') ) { site.body.addClass('introducing'); } 238 var pm = jQuery(nextElm).closest('.pagemark'), res; 239 if ( pm.length ) { 240 res = pm.attr('class').match(/\b(pm[0-9]+)/); 241 if ( res && res[1] ) { 242 if ( site.parallax.jumpTo(res[1]) ) { 243 intro.hide(); 244 setTimeout(function(){ 245 intro.refresh(); 246 intro.show(); 247 }, 3000); 248 } 249 } 250 } 251 else { 252 intro.show(); 253 } 254 }); 255 intro.onexit(function(){ if ( site.body.is('.introducing') ) { site.body.removeClass('introducing'); } }); 256 intro.oncomplete(function(){ if ( site.body.is('.introducing') ) { site.body.removeClass('introducing'); } }); 257 return intro; 258 }, 259 260 /** 261 * Hook into the getParallaxBuilder functionality 262 * 263 * @memberof! site 264 */ 265 createParallax: function(siteData){ 266 return parallax.create(siteData.parallax, this); 267 }, 268 269 /** 270 * For each page of content that is specified in the JSON, 271 * attempt to load the corresponding HTML file. 272 * 273 * @memberof! site 274 */ 275 createContent: function(siteData){ 276 var pageKey, item, pocket, when = [], totals = [], content = {}, d = jQuery.Deferred(), 277 progressTracker = function(i){ 278 totals[i] = 0; 279 return function(p){ 280 var k, total = 0; 281 totals[i] = p; 282 for ( k=0; k<totals.length; k++ ) { total += totals[k]; } 283 d.notify(1/when.length * total); 284 }; 285 }, 286 contentTracker = function(pageKey){ 287 return function(c){ 288 content[pageKey] = c; 289 }; 290 } 291 ; 292 try { 293 if ( siteData.pages ) { 294 for ( pageKey in siteData.pages ) { 295 if ( (item=siteData.pages[pageKey]) ) { 296 if ( item.pocket ) { 297 when.push(jQuery.getPocketTemplate(item.pocket) 298 .progress(progressTracker(when.length)) 299 .done(contentTracker(pageKey)) 300 ); 301 } 302 } 303 } 304 } 305 jQuery.when.apply(jQuery, when) 306 .done(function(){d.resolve(content);}) 307 .fail(function(){d.reject();}) 308 ; 309 } 310 catch ( ex ) { 311 console.log(ex); 312 } 313 return d.promise(); 314 }, 315 316 /** 317 * @TODO: this is disabled for now as it wasn't yet being used 318 */ 319 animate: function(now){ 320 !this.config.stopped && this.config.raf.call(window, this.animate); 321 if ( now ) { 322 this.config.delta = ( this.config.then 323 ? now - this.config.then 324 : this.config.interval 325 ); 326 if ( this.config.delta >= this.config.interval ) { 327 this.config.then = now - (this.config.delta % this.config.interval); 328 //tick(1 - (1 / interval) * (delta % interval)); 329 //tick(delta / interval); 330 this.tick(this.config.interval / this.config.delta); 331 } 332 } 333 }, 334 335 /** 336 * 337 */ 338 start: function(){ 339 this.config.stopped = false; 340 //this.animate(); 341 }, 342 343 /** 344 * 345 */ 346 stop: function(){ 347 this.config.stopped = true; 348 }, 349 /** 350 * Test that we are in fullscreen mode 351 * 352 * @memberof! site 353 */ 354 isFullscreen: function(){ 355 return ( 356 (document.fullScreenElement 357 && document.fullScreenElement !== null) || 358 (document.mozFullScreenElement 359 && document.mozFullScreenElement !== null) || 360 (document.webkitFullscreenElement 361 && document.webkitFullscreenElement !== null) || 362 (document.msFullscreenElement 363 && document.msFullscreenElement !== null) || 364 document.mozFullScreen || 365 document.webkitIsFullScreen 366 ); 367 }, 368 /** 369 * Toggle fullscreen 370 * 371 * @memberof! site 372 */ 373 fullscreen: function(){ 374 if (!document.fullscreenElement && 375 !document.mozFullScreenElement && 376 !document.webkitFullscreenElement && 377 !document.msFullscreenElement ) 378 { 379 if (document.documentElement.requestFullscreen) { document.documentElement.requestFullscreen(); } 380 else if (document.documentElement.msRequestFullscreen) { document.documentElement.msRequestFullscreen(); } 381 else if (document.documentElement.mozRequestFullScreen) { document.documentElement.mozRequestFullScreen(); } 382 else if (document.documentElement.webkitRequestFullscreen) { document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); } 383 } 384 else { 385 if (document.exitFullscreen) { document.exitFullscreen(); } 386 else if (document.msExitFullscreen) { document.msExitFullscreen(); } 387 else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } 388 else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); } 389 } 390 }, 391 392 /** 393 * Add a class to the main wrapping element 394 * 395 * @TODO: currently hardcoded to body, should be improved. 396 * 397 * @memberof! site 398 */ 399 addClass: function(classname){ 400 jQuery('body').addClass(classname); 401 }, 402 /** 403 * Remove a class to the main wrapping element 404 * 405 * @TODO: currently hardcoded to body, should be improved. 406 * 407 * @memberof! site 408 */ 409 removeClass: function(classname){ 410 jQuery('body').removeClass(classname); 411 }, 412 413 /** 414 * Here you will find all the bespoke events that codelamp.co.uk uses 415 * 416 * @memberof! site 417 * @namespace! site.events 418 */ 419 events: { 420 /** 421 * triggered when fullscreen changes 422 */ 423 fullscreenchange: function(){ 424 if ( site.isFullscreen() ) { 425 site.addClass('fullscreened'); 426 } 427 else { 428 site.removeClass('fullscreened'); 429 } 430 } 431 432 }, 433 434 /** 435 * Toggle the "power" state of the website 436 * 437 * @memberof! site 438 */ 439 power: function(icon){ 440 var elm = jQuery(icon).find('i'), cls = elm.attr('class'), pow, nex; 441 switch ( cls ) { 442 case 'fa fa-battery-0': pow = 0; break; 443 case 'fa fa-battery-1': pow = 1; break; 444 case 'fa fa-battery-2': pow = 2; break; 445 case 'fa fa-battery-3': pow = 3; break; 446 case 'fa fa-battery-4': pow = 4; break; 447 } 448 nex = (pow+1)%5; 449 elm.removeClass('fa-battery-' + pow).addClass('fa-battery-'+nex); 450 jQuery('body').removeClass('power-' + pow).addClass('power-'+nex); 451 } 452 453 }; 454 455 /** 456 * Specialised extension to jQuery to load our "pocket" templates 457 * 458 * @memberof! jQuery 459 */ 460 jQuery.getPocketTemplate = function(url){ 461 var d = jQuery.Deferred(); 462 var config = { 463 type: 'get', 464 url: url, 465 cache: false, 466 xhr: function(xhr){ 467 xhr = jQuery.ajaxSettings.xhr(); 468 config.notify = function(e){ if ( isNaN(e) ) { e.lengthComputable && d.notify(e.loaded / e.total); } else { d.notify(e); } }; 469 config.unnotify = function(){ xhr.removeEventListener("progress", config.notify); }; 470 xhr.addEventListener("progress", config.notify, false); 471 return xhr; 472 } 473 }; 474 try { 475 $.ajax(config) 476 // due to progress events not being fired when using cached content, .notify(1) is required 477 .always(function(){ config.notify(1); config.unnotify();}) 478 .done(function(data){ 479 var template = {}; 480 template.elements = pockets.parsePocketTemplate(data); 481 template.sections = template.elements.filter('section'); 482 template.displays = {}; 483 /// if any sections have been defined, use these as template separators 484 if ( template.sections.length ) { 485 template.sections.each(function(){ 486 template.displays[this.className] = pockets.parsePocketDisplay(this, this.className); 487 }); 488 } 489 else { 490 template.displays['default'] = pockets.parsePocketDisplay(template.elements, 'default'); 491 } 492 delete template.elements; 493 delete template.sections; 494 d.resolve(template); 495 }) 496 ; 497 } 498 catch (ex) { console.log(ex); } 499 return d.promise(); 500 }; 501 502 /** 503 * Same as jQuery each, but with promise and the possibility of 504 * fetching data from elsewhere. 505 * 506 * @memberof! jQuery 507 */ 508 jQuery.eachResolve = function(data, callback, context){ 509 var d = jQuery.Deferred(); 510 jQuery.each(data, jQuery.proxy(callback, context)); 511 return d.resolveWith(context, [context]).promise(); 512 }; 513 514 /** 515 * Find links with `data-preview` attributes and set interaction events 516 * to display that data as a thumb image on hover. That should serve as a thumbnail 517 * preview of the link itself. 518 * 519 * I'm using images, rather than live previews, because it is a much lighter 520 * solution; that works even for remote/frame-protected websites. 521 * 522 * @memberof! jQuery 523 * @method thumbPreview 524 * @returns {undefined} 525 */ 526 jQuery.fn.thumbPreview = function(){ 527 this.each(function(){ 528 var self = jQuery(this), 529 img = jQuery('<img src="' + self.attr('data-preview') + '" />'), 530 enter = function(){ 531 self 532 .addClass('thumb-preview') 533 .prepend(img) 534 ; 535 }, 536 leave = function(){ 537 self.removeClass('thumb-preview'); 538 img.remove(); 539 }, 540 toi 541 ; 542 img.on('load', function(){ 543 this.style.marginLeft = (-this.width/2) + 'px'; 544 }); 545 self 546 .on('mouseenter', function(e){ 547 toi && clearTimeout(toi); 548 toi = setTimeout(enter, 1000); 549 }) 550 .on('mouseleave', function(e){ 551 toi && clearTimeout(toi); 552 leave(); 553 }); 554 ; 555 }); 556 }; 557 558 jQuery.fn.isNot = function(){ 559 return !this.is.apply(this, arguments); 560 }; 561 562 return site; 563 564}; 565 566/** 567 * @memberof! makeshift 568 * @deprecated for now, until I can get more time, will create the parallax in Canvas/WebGL rather than CSS/HTML transitions 569 */ 570makeshift.generatePixiSite = function(){ 571 /// layers[layers.topmost].inner.append('<div id="jumptest"><h1 style="color: white;">Hello</h1></div>'); 572 var body = jQuery('body'); 573 var wrapper = jQuery('<div />').addClass('wrapper').appendTo(body); 574 var site = { 575 create: function(config){ 576 return this.prep.apply(Object.create(this), arguments); 577 }, 578 prep: function(config){ 579 this.i = {}; 580 this.i.steps = {createStage: 1, createLayers: 1}; 581 return this; 582 }, 583 build: function(){}, 584 createStage: function(){}, 585 createLayers: function(){} 586 }; 587 PIXI.loader 588 .add('sky', 'images/background-sky-big-3.jpg') 589 .add('bg-2', 'images/background-landmass-2-8b.png') 590 .add('fg-3', 'images/foreground-landmass-3.1-8b.png') 591 .add('fg-2', 'images/foreground-landmass-2-80px-8b.png') 592 .add('fg-1', 'images/foreground-landmass-extended-80px-8b.png') 593 .add('mist-1', 'images/solid-mist-8b.png') 594 .add('mist-2', 'images/solid-mist-8b.png') 595 .on('progress', function(loader, loadedResource){console.log('Progress:', loader.progress + '%');}) 596 .load(function (loader, resources) {}) 597 ; 598 return { 599 create: function(){} 600 }; 601};
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.