Scroll effect / Image transition test for a project using PIXI for Webgl abstraction. Effect is made by drawing the image, scaling x, applying threshold and using that as a mask.
A Pen by Felix Nielsen on CodePen.
Scroll effect / Image transition test for a project using PIXI for Webgl abstraction. Effect is made by drawing the image, scaling x, applying threshold and using that as a mask.
A Pen by Felix Nielsen on CodePen.
| <!-- use your scroll-wheel --> |
| // ___ ___ ___ ___ ___ | |
| // /\ \ /\ \ /\__\ /\ \ |\__\ | |
| // /::\ \ /::\ \ /:/ / /::\ \ |:| | | |
| // /:/\:\ \ /:/\:\ \ /:/ / /:/\:\ \ |:| | | |
| // /::\~\:\ \ /::\~\:\ \ /:/ / /::\~\:\ \ |:|__|__ | |
| // /:/\:\ \:\__\ /:/\:\ \:\__\ /:/__/ /:/\:\ \:\__\ ____/::::\__\ | |
| // \/_|::\/:/ / \:\~\:\ \/__/ \:\ \ \/__\:\/:/ / \::::/~~/~ | |
| // |:|::/ / \:\ \:\__\ \:\ \ \::/ / ~~|:|~~| | |
| // |:|\/__/ \:\ \/__/ \:\ \ /:/ / |:| | | |
| // |:| | \:\__\ \:\__\ /:/ / |:| | | |
| // \|__| \/__/ \/__/ \/__/ \|__| | |
| // and use your scrollwheel | |
| // relaxwearethegoodguys.com | |
| // Much love to https://twitter.com/tokotokostudio for a fun exploration process | |
| // Image credits --> https://www.behance.net/gallery/34853303/Destroying-nature-is-destroying-life | |
| // Tweek the zoom values of each Transition object.. | |
| // Different images creates various effects.. | |
| var getBetweenColourByPercent = function (percent, highColor, lowColor) { | |
| var r = highColor >> 16; | |
| var g = highColor >> 8 & 0xFF; | |
| var b = highColor & 0xFF; | |
| r += ((lowColor >> 16) - r) * percent; | |
| g += ((lowColor >> 8 & 0xFF) - g) * percent; | |
| b += ((lowColor & 0xFF) - b) * percent; | |
| var hex = (r << 16 | g << 8 | b); | |
| return "#" + hex.toString(16); | |
| }; | |
| var Application = (function () { | |
| function Application() { | |
| // should be overwritten | |
| var _this = this; | |
| this.introTweening = true; | |
| this.colors = [0xffffff, 0xbcb7a4, 0xb4bec2, 0xbcb7a4, 0xb4bec2, 0xffffff]; | |
| this.infiniteScrollPos = 0; | |
| this.direction = -1; | |
| this.wheelSpeed = 0; | |
| this.yRoller = 0; | |
| this.progressBar = document.createElement("div"); | |
| this.progressBar.id = "progressbar"; | |
| document.body.appendChild(this.progressBar); | |
| (window.TweenPlugin).activate([window.CSSPlugin]); | |
| // create a renderer instance. | |
| this.renderer = new PIXI.autoDetectRenderer(1400, 990, { | |
| autoResize: true, | |
| resolution: 1, | |
| transparent: true | |
| }); | |
| // this.renderer.backgroundColor = 0x000000; | |
| // add the renderer view element to the DOM | |
| document.body.appendChild(this.renderer.view); | |
| this.stage = new PIXI.Container(); | |
| this.transitions = []; | |
| this.transitions.push(new Transition({ | |
| renderer: this.renderer, | |
| stage: this.stage, | |
| image: "http://rwatggcdn.blob.core.windows.net/cdn/codepens/cp-5-01.jpg", | |
| index: this.transitions.length, | |
| zoom: 4 | |
| })); | |
| this.transitions.push(new Transition({ | |
| renderer: this.renderer, | |
| stage: this.stage, | |
| image: "http://rwatggcdn.blob.core.windows.net/cdn/codepens/cp-5-02.jpg", | |
| index: this.transitions.length, | |
| zoom: 4 | |
| })); | |
| this.transitions.push(new Transition({ | |
| renderer: this.renderer, | |
| stage: this.stage, | |
| image: "http://rwatggcdn.blob.core.windows.net/cdn/codepens/cp-5-01.jpg", | |
| index: this.transitions.length, | |
| zoom: 2 | |
| })); | |
| this.transitions.push(new Transition({ | |
| renderer: this.renderer, | |
| stage: this.stage, | |
| image: "http://rwatggcdn.blob.core.windows.net/cdn/codepens/cp-5-02.jpg", | |
| index: this.transitions.length, | |
| zoom: 6 | |
| })); | |
| this.render(); | |
| window.addEventListener("resize", this.resize.bind(this), false); | |
| document.addEventListener('DOMMouseScroll', this.handleScroll.bind(this), false); // for Firefox | |
| document.addEventListener('mousewheel', this.handleScroll.bind(this), false); | |
| document.addEventListener("touchstart", this.onTouchStart.bind(this), false); | |
| document.addEventListener("touchmove", this.onTouchMove.bind(this), false); | |
| TweenLite.to(this, 1, { | |
| yRoller: (window.innerWidth / 2), | |
| ease: "easeOutCubic", | |
| delay: 0.25, | |
| onStart: function () { return _this.resize(null); }, | |
| onComplete: function () { | |
| _this.introTweening = false; | |
| } | |
| }); | |
| } | |
| Application.prototype.onTouchStart = function (event) { | |
| event.preventDefault(); | |
| this.touchStartY = event.touches[0].pageY; | |
| }; | |
| Application.prototype.onTouchMove = function (event) { | |
| event.preventDefault(); | |
| this.wheelSpeed = (event.touches[0].pageY - this.touchStartY); | |
| if (this.wheelSpeed > 0) | |
| this.direction = 1; | |
| else if (this.wheelSpeed < 0) | |
| this.direction = -1; | |
| this.touchStartY = event.touches[0].pageY; | |
| }; | |
| Application.prototype.handleScroll = function (event) { | |
| var normalized; | |
| if (event.wheelDelta) { | |
| normalized = (event.wheelDelta % 120 - 0) == -0 ? event.wheelDelta / 120 : event.wheelDelta / 12; | |
| } | |
| else { | |
| var rawAmmount = event.deltaY ? event.deltaY : event.detail; | |
| normalized = -(rawAmmount % 3 ? rawAmmount * 10 : rawAmmount / 3); | |
| } | |
| this.wheelSpeed = (normalized * 2); | |
| if (this.wheelSpeed > 0) | |
| this.direction = 1; | |
| else if (this.wheelSpeed < 0) | |
| this.direction = -1; | |
| }; | |
| Application.prototype.render = function () { | |
| // this.noiseFilter.padding += 1; | |
| var _this = this; | |
| if (!this.introTweening) { | |
| this.wheelSpeed += (0 - this.wheelSpeed) * 0.05; | |
| this.yRoller -= this.wheelSpeed; | |
| } | |
| var numItems = this.transitions.length - 1; | |
| if (this.yRoller > window.innerWidth * numItems) | |
| this.yRoller = (window.innerWidth * numItems); | |
| if (this.yRoller < 0) | |
| this.yRoller = 0; | |
| var px = this.yRoller / window.innerWidth; | |
| var progress = px * Application.overlap; | |
| var p = px / numItems; | |
| // colors | |
| var l = this.colors.length - 1; | |
| var colorProgress = p * l; | |
| var colorIndex = Math.floor(colorProgress); | |
| colorProgress = colorProgress - colorIndex; | |
| var startColor = this.colors[colorIndex]; | |
| var endColor = this.colors[colorIndex + 1]; | |
| var color = getBetweenColourByPercent(colorProgress, startColor, endColor); | |
| TweenLite.set(document.body, { | |
| backgroundColor: color | |
| }); | |
| if (this.progressBar) { | |
| TweenLite.set(this.progressBar, { | |
| force3D: true, | |
| scaleX: p | |
| }); | |
| } | |
| for (var i = 0; i < this.transitions.length; i++) { | |
| var element = this.transitions[i]; | |
| element.render(px); | |
| } | |
| this.renderer.render(this.stage); | |
| requestAnimationFrame(function () { return _this.render(); }); | |
| }; | |
| Object.defineProperty(Application, "viewport", { | |
| get: function () { | |
| var ratio = 990 / 1400; | |
| var w = window.innerWidth; | |
| var h = window.innerWidth * ratio; | |
| if (h > window.innerHeight) { | |
| h = window.innerHeight; | |
| w = (1400 / 990) * h; | |
| } | |
| return { | |
| w: w, | |
| h: h | |
| }; | |
| }, | |
| enumerable: true, | |
| configurable: true | |
| }); | |
| Application.prototype.resize = function (event) { | |
| var rect = Application.viewport; | |
| this.renderer.view.style.width = rect.w + "px"; | |
| this.renderer.view.style.height = rect.h + "px"; | |
| this.renderer.view.style.left = ((window.innerWidth * 0.5) - (rect.w * 0.5)) + "px"; | |
| this.renderer.view.style.top = ((window.innerHeight * 0.5) - (rect.h * 0.5)) + "px"; | |
| // this.renderer.resize(rect.w, rect.h); | |
| // for (var i = 0; i < this.transitions.length; i++) { | |
| // var element: Transition = this.transitions[i]; | |
| // element.resize(rect.w, rect.h); | |
| // } | |
| }; | |
| Application.overlap = 0.6; | |
| return Application; | |
| }()); | |
| // starter code | |
| window.startSingle = function () { | |
| // should be overwritten with Master Application class | |
| window.application = new Application(); | |
| }; | |
| var Transition = (function () { | |
| function Transition(options) { | |
| var _this = this; | |
| this.progressTarget = 0; | |
| this.renderer = options.renderer; | |
| this.index = options.index; | |
| this.zoom = options.zoom; | |
| this.maskRenderer = new PIXI.autoDetectRenderer(this.renderer.width, this.renderer.height, { | |
| autoResize: true | |
| }); | |
| this.container = new PIXI.Container(); | |
| options.stage.addChild(this.container); | |
| // add the mask for visual ref | |
| // document.getElementById("page-content").appendChild(this.maskRenderer.view); | |
| var texture; | |
| // actual image | |
| texture = PIXI.Texture.fromImage(options.image); | |
| texture.baseTexture.on('loaded', function (baseTexture) { | |
| var rect = Application.viewport; | |
| _this.resize(rect.w, rect.h); | |
| }); | |
| this.imageToBeMasked = new PIXI.Sprite(texture); | |
| // mask image | |
| texture = PIXI.Texture.fromImage("http://rwatggcdn.blob.core.windows.net/cdn/codepens/dm-02.jpg"); | |
| this.mask = new PIXI.Sprite(texture); | |
| // displacement map, a copy of the original | |
| texture = PIXI.Texture.fromImage(options.image); | |
| this.imageDisplacementSprite = new PIXI.Sprite(texture); | |
| this.container.addChild(this.imageDisplacementSprite); | |
| this.maskDisplacementFilter = new PIXI.filters.DisplacementFilter(this.imageDisplacementSprite); | |
| this.mask.filters = [this.maskDisplacementFilter]; | |
| this.maskToBeDrawTo_AKA_cache = new PIXI.Sprite(); | |
| this.imageToBeMasked.mask = this.maskToBeDrawTo_AKA_cache; | |
| this.container.addChild(this.imageToBeMasked); | |
| } | |
| Transition.prototype.resize = function (w, h) { | |
| this.hasResized = true; | |
| var ratio = h / w; | |
| var zoom = isNaN(this.zoom) ? 4 : this.zoom; | |
| this.imageDisplacementSprite.width = w * zoom; | |
| this.imageDisplacementSprite.height = h * zoom; | |
| this.imageDisplacementSprite.texture.width = w * zoom; | |
| this.imageDisplacementSprite.texture.height = h * zoom; | |
| this.imageDisplacementSprite.x = (this.imageDisplacementSprite.width * -0.5) + (w * 0.5); | |
| this.imageDisplacementSprite.y = (this.imageDisplacementSprite.height * -0.5) + (h * 0.5); | |
| this.imageDisplacementSprite.texture.requiresUpdate = true; | |
| this.mask.width = this.renderer.width; | |
| this.mask.height = (this.renderer.height * 3) + 200; // + 200 because of distortion overlap | |
| this.mask.texture.requiresUpdate = true; | |
| this.imageToBeMasked.texture.width = w; | |
| this.imageToBeMasked.texture.height = h; | |
| this.imageToBeMasked.texture.requiresUpdate = true; | |
| // center | |
| this.container.pivot.x = w * 0.5; | |
| this.container.pivot.y = h * 0.5; | |
| this.container.x = w * 0.5; | |
| this.container.y = h * 0.5; | |
| this.maskToBeDrawTo_AKA_cache.texture.width = w; | |
| this.maskToBeDrawTo_AKA_cache.texture.height = h; | |
| this.maskToBeDrawTo_AKA_cache.texture.requiresUpdate = true; | |
| // this.maskRenderer.resize(w, h); | |
| }; | |
| Transition.prototype.render = function (progress) { | |
| if (!this.hasResized) | |
| return; | |
| progress = progress - (this.index * Application.overlap); //overlap.. | |
| if (progress < -0.2 || progress > 1.2) | |
| return; | |
| var filterProgress = 1 - Math.abs((0.5 - (progress)) / 0.5); | |
| var start = Math.min(1, progress / 0.1); | |
| if (progress > 0.5) | |
| start = 1; | |
| var end = Math.min(1, Math.max(0, progress - 0.5) / 0.1); | |
| if (progress < 0.5) | |
| end = 1; | |
| filterProgress *= start; | |
| filterProgress *= end; | |
| var scale = 1 + (progress * -0.2); | |
| this.container.scale.x = scale; | |
| this.container.scale.y = scale; | |
| // manipulate this.mask displacement map filter | |
| this.maskDisplacementFilter.scale.x = (filterProgress) * 600; | |
| this.maskDisplacementFilter.scale.y = (filterProgress) * 600; | |
| var overflowy = (progress * this.mask.height); | |
| this.mask.y = overflowy * -1; | |
| this.mask.y += progress * this.maskRenderer.height; | |
| this.maskRenderer.render(this.mask); | |
| // draw image from mask Sprite | |
| this.maskToBeDrawTo_AKA_cache.texture = PIXI.Texture.fromCanvas(this.maskRenderer.view); | |
| this.maskToBeDrawTo_AKA_cache.texture.update(); | |
| }; | |
| return Transition; | |
| }()); | |
| // starter code | |
| window.start = function () { | |
| // should be overwritten with Master Application class | |
| window.application = new Application(); | |
| }; | |
| if (document.readyState == "complete" || document.readyState == "interactive") { | |
| if (!window.application) | |
| window.start(); | |
| } | |
| document.addEventListener("DOMContentLoaded", function (event) { | |
| if (!window.application) | |
| window.start(); | |
| }, false); |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.18.4/TweenMax.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/3.0.11/pixi.min.js"></script> |
| html, body | |
| background #ffffff | |
| width 100% | |
| height 100vh | |
| margin 0px | |
| padding 0px | |
| border 0px | |
| outline 0px | |
| overflow hidden | |
| #progressbar | |
| top 0px | |
| left 0px | |
| position fixed | |
| width 100% | |
| height 5px | |
| background white | |
| transform-origin 0% 0% | |
| z-index 666 | |
| canvas | |
| position absolute |