├── .gitignore ├── css └── styles.css ├── scss └── styles.scss ├── index.html ├── demos ├── events │ ├── index.html │ ├── shaders │ │ ├── backgroundFragment.glsl │ │ └── stageFragment.glsl │ └── js │ │ └── scripts.js ├── images │ ├── index.html │ ├── shaders │ │ ├── backgroundFragment.glsl │ │ └── stageFragment.glsl │ └── js │ │ └── scripts.js ├── animation │ ├── index.html │ ├── shaders │ │ ├── backgroundFragment.glsl │ │ └── stageFragment.glsl │ └── js │ │ └── scripts.js ├── background │ ├── index.html │ ├── shaders │ │ └── backgroundFragment.glsl │ └── js │ │ └── scripts.js ├── distortion │ ├── index.html │ ├── shaders │ │ ├── backgroundFragment.glsl │ │ └── stageFragment.glsl │ └── js │ │ └── scripts.js ├── init-pixi │ ├── index.html │ └── js │ │ └── scripts.js ├── final │ ├── index.html │ ├── shaders │ │ ├── backgroundFragment.glsl │ │ └── stageFragment.glsl │ └── js │ │ └── scripts.js ├── rects-final │ ├── index.html │ ├── shaders │ │ ├── backgroundFragment.glsl │ │ └── stageFragment.glsl │ └── js │ │ └── scripts.js ├── rects-pixi │ ├── index.html │ ├── shaders │ │ ├── backgroundFragment.glsl │ │ └── stageFragment.glsl │ └── js │ │ └── scripts.js └── rects │ ├── index.html │ └── js │ └── scripts.js ├── README.md ├── gulpfile.js ├── package.json ├── LICENSE ├── shaders ├── backgroundFragment.glsl └── stageFragment.glsl └── js └── scripts.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | temp/ 3 | -------------------------------------------------------------------------------- /css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | overflow: hidden; 4 | background-color: black; 5 | } 6 | -------------------------------------------------------------------------------- /scss/styles.scss: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | overflow: hidden; 4 | background-color: black; 5 | } 6 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Images Gallery using PixiJS and WebGL 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demos/events/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Images Gallery - Events 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demos/images/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Images Gallery - Images 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demos/animation/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Images Gallery - Animation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demos/background/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Images Gallery - Background 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demos/distortion/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Images Gallery - Distortion 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demos/init-pixi/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Images Gallery - Init PixiJS 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demos/final/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Images Gallery using PixiJS and WebGL 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demos/rects-final/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Images Gallery - Rects Final 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demos/rects-pixi/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Images Gallery - Rects in PixiJS 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demos/rects/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Images Gallery - Rects 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Images Gallery 2 | 3 | Images Gallery using PixiJS and WebGL 4 | 5 | - **Tutorial**: [https://css-tricks.com/building-an-images-gallery-using-pixijs-and-webgl/](https://css-tricks.com/building-an-images-gallery-using-pixijs-and-webgl/) 6 | - **Demo**: [https://lmgonzalves.github.io/images-gallery/](https://lmgonzalves.github.io/images-gallery/) 7 | - **Codepen**: [https://codepen.io/lmgonzalves/pen/oRdwQJ](https://codepen.io/lmgonzalves/pen/oRdwQJ) 8 | 9 | ## Credits 10 | 11 | - Inspired by a [Dribbble shot by Zhenya Rynzhuk](https://dribbble.com/shots/5490545-Blown-Art-Works-and-News-Platform-Talents-Page-Animation). 12 | - Images are from [Unsplash](https://unsplash.com), using the [Unsplash Source API](https://source.unsplash.com/). 13 | -------------------------------------------------------------------------------- /demos/init-pixi/js/scripts.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Get canvas view 4 | const view = document.querySelector('.view') 5 | let width, height, app 6 | 7 | // Set dimensions 8 | function initDimensions () { 9 | width = window.innerWidth 10 | height = window.innerHeight 11 | } 12 | 13 | // Init the PixiJS Application 14 | function initApp () { 15 | // Create a PixiJS Application, using the view (canvas) provided 16 | app = new PIXI.Application({ view }) 17 | // Resizes renderer view in CSS pixels to allow for resolutions other than 1 18 | app.renderer.autoDensity = true 19 | // Resize the view to match viewport dimensions 20 | app.renderer.resize(width, height) 21 | } 22 | 23 | // Init everything 24 | function init () { 25 | initDimensions() 26 | initApp() 27 | } 28 | // Initial call 29 | init() 30 | 31 | })() 32 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | var browserSync = require('browser-sync') 3 | var sass = require('gulp-sass') 4 | var prefix = require('gulp-autoprefixer') 5 | 6 | gulp.task('serve', ['sass'], function () { 7 | browserSync.init({ 8 | server: { 9 | baseDir: './' 10 | }, 11 | open: false, 12 | online: false, 13 | notify: false 14 | }) 15 | 16 | gulp.watch('scss/*.scss', ['sass']) 17 | gulp.watch(['**/*.html', 'js/*', 'shaders/*']).on('change', browserSync.reload) 18 | }) 19 | 20 | gulp.task('sass', function () { 21 | return gulp.src('scss/*.scss') 22 | .pipe(sass({ 23 | outputStyle: 'expanded', 24 | includePaths: ['scss'] 25 | })) 26 | .pipe(prefix(['last 5 versions'], { cascade: true })) 27 | .pipe(gulp.dest('css')) 28 | .pipe(browserSync.reload({ stream: true })) 29 | }) 30 | 31 | gulp.task('default', ['serve']) 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "images-gallery", 3 | "version": "0.0.1", 4 | "description": "Images Gallery using PixiJS and WebGL", 5 | "main": "js/scripts.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/lmgonzalves/images-gallery.git" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "keywords": [ 14 | "javascript", 15 | "animation", 16 | "webgl" 17 | ], 18 | "author": "lmgonzalves", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/lmgonzalves/images-gallery/issues" 22 | }, 23 | "homepage": "https://github.com/lmgonzalves/images-gallery", 24 | "devDependencies": { 25 | "browser-sync": "^2.23.6", 26 | "gulp": "^3.9.1", 27 | "gulp-autoprefixer": "^5.0.0", 28 | "gulp-rename": "^1.2.2", 29 | "gulp-sass": "^3.1.0", 30 | "gulp-uglify": "^3.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /demos/events/shaders/backgroundFragment.glsl: -------------------------------------------------------------------------------- 1 | // Adapted from: https://www.shadertoy.com/view/MdlGRr 2 | 3 | // It is required to set the float precision for fragment shaders in OpenGL ES 4 | // More info here: https://stackoverflow.com/a/28540641/4908989 5 | #ifdef GL_ES 6 | precision mediump float; 7 | #endif 8 | 9 | // This function returns 1 if `coord` correspond to a grid line, 0 otherwise 10 | float isGridLine (vec2 coord) { 11 | vec2 pixelsPerGrid = vec2(50.0, 50.0); 12 | vec2 gridCoords = fract(coord / pixelsPerGrid); 13 | vec2 gridPixelCoords = gridCoords * pixelsPerGrid; 14 | vec2 gridLine = step(gridPixelCoords, vec2(1.0)); 15 | float isGridLine = max(gridLine.x, gridLine.y); 16 | return isGridLine; 17 | } 18 | 19 | // Main function 20 | void main () { 21 | // Coordinates for the current pixel 22 | vec2 coord = gl_FragCoord.xy; 23 | // Set `color` to black 24 | vec3 color = vec3(0.0); 25 | // If it is a grid line, change blue channel to 0.3 26 | color.b = isGridLine(coord) * 0.3; 27 | // Assing the final rgba color to `gl_FragColor` 28 | gl_FragColor = vec4(color, 1.0); 29 | } 30 | -------------------------------------------------------------------------------- /demos/background/shaders/backgroundFragment.glsl: -------------------------------------------------------------------------------- 1 | // Adapted from: https://www.shadertoy.com/view/MdlGRr 2 | 3 | // It is required to set the float precision for fragment shaders in OpenGL ES 4 | // More info here: https://stackoverflow.com/a/28540641/4908989 5 | #ifdef GL_ES 6 | precision mediump float; 7 | #endif 8 | 9 | // This function returns 1 if `coord` correspond to a grid line, 0 otherwise 10 | float isGridLine (vec2 coord) { 11 | vec2 pixelsPerGrid = vec2(50.0, 50.0); 12 | vec2 gridCoords = fract(coord / pixelsPerGrid); 13 | vec2 gridPixelCoords = gridCoords * pixelsPerGrid; 14 | vec2 gridLine = step(gridPixelCoords, vec2(1.0)); 15 | float isGridLine = max(gridLine.x, gridLine.y); 16 | return isGridLine; 17 | } 18 | 19 | // Main function 20 | void main () { 21 | // Coordinates for the current pixel 22 | vec2 coord = gl_FragCoord.xy; 23 | // Set `color` to black 24 | vec3 color = vec3(0.0); 25 | // If it is a grid line, change blue channel to 0.3 26 | color.b = isGridLine(coord) * 0.3; 27 | // Assing the final rgba color to `gl_FragColor` 28 | gl_FragColor = vec4(color, 1.0); 29 | } 30 | -------------------------------------------------------------------------------- /demos/distortion/shaders/backgroundFragment.glsl: -------------------------------------------------------------------------------- 1 | // Adapted from: https://www.shadertoy.com/view/MdlGRr 2 | 3 | // It is required to set the float precision for fragment shaders in OpenGL ES 4 | // More info here: https://stackoverflow.com/a/28540641/4908989 5 | #ifdef GL_ES 6 | precision mediump float; 7 | #endif 8 | 9 | // This function returns 1 if `coord` correspond to a grid line, 0 otherwise 10 | float isGridLine (vec2 coord) { 11 | vec2 pixelsPerGrid = vec2(50.0, 50.0); 12 | vec2 gridCoords = fract(coord / pixelsPerGrid); 13 | vec2 gridPixelCoords = gridCoords * pixelsPerGrid; 14 | vec2 gridLine = step(gridPixelCoords, vec2(1.0)); 15 | float isGridLine = max(gridLine.x, gridLine.y); 16 | return isGridLine; 17 | } 18 | 19 | // Main function 20 | void main () { 21 | // Coordinates for the current pixel 22 | vec2 coord = gl_FragCoord.xy; 23 | // Set `color` to black 24 | vec3 color = vec3(0.0); 25 | // If it is a grid line, change blue channel to 0.3 26 | color.b = isGridLine(coord) * 0.3; 27 | // Assing the final rgba color to `gl_FragColor` 28 | gl_FragColor = vec4(color, 1.0); 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 lmgonzalves 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /demos/animation/shaders/backgroundFragment.glsl: -------------------------------------------------------------------------------- 1 | // Adapted from: https://www.shadertoy.com/view/MdlGRr 2 | 3 | // It is required to set the float precision for fragment shaders in OpenGL ES 4 | // More info here: https://stackoverflow.com/a/28540641/4908989 5 | #ifdef GL_ES 6 | precision mediump float; 7 | #endif 8 | 9 | uniform vec2 uPointerDiff; 10 | 11 | // This function returns 1 if `coord` correspond to a grid line, 0 otherwise 12 | float isGridLine (vec2 coord) { 13 | vec2 pixelsPerGrid = vec2(50.0, 50.0); 14 | vec2 gridCoords = fract(coord / pixelsPerGrid); 15 | vec2 gridPixelCoords = gridCoords * pixelsPerGrid; 16 | vec2 gridLine = step(gridPixelCoords, vec2(1.0)); 17 | float isGridLine = max(gridLine.x, gridLine.y); 18 | return isGridLine; 19 | } 20 | 21 | // Main function 22 | void main () { 23 | // Coordinates minus the `uPointerDiff` value 24 | vec2 coord = gl_FragCoord.xy - uPointerDiff; 25 | // Set `color` to black 26 | vec3 color = vec3(0.0); 27 | // If it is a grid line, change blue channel to 0.3 28 | color.b = isGridLine(coord) * 0.3; 29 | // Assing the final rgba color to `gl_FragColor` 30 | gl_FragColor = vec4(color, 1.0); 31 | } 32 | -------------------------------------------------------------------------------- /demos/rects-pixi/shaders/backgroundFragment.glsl: -------------------------------------------------------------------------------- 1 | // Adapted from: https://www.shadertoy.com/view/MdlGRr 2 | 3 | // It is required to set the float precision for fragment shaders in OpenGL ES 4 | // More info here: https://stackoverflow.com/a/28540641/4908989 5 | #ifdef GL_ES 6 | precision mediump float; 7 | #endif 8 | 9 | uniform vec2 uPointerDiff; 10 | 11 | // This function returns 1 if `coord` correspond to a grid line, 0 otherwise 12 | float isGridLine (vec2 coord) { 13 | vec2 pixelsPerGrid = vec2(50.0, 50.0); 14 | vec2 gridCoords = fract(coord / pixelsPerGrid); 15 | vec2 gridPixelCoords = gridCoords * pixelsPerGrid; 16 | vec2 gridLine = step(gridPixelCoords, vec2(1.0)); 17 | float isGridLine = max(gridLine.x, gridLine.y); 18 | return isGridLine; 19 | } 20 | 21 | // Main function 22 | void main () { 23 | // Coordinates minus the `uPointerDiff` value 24 | vec2 coord = gl_FragCoord.xy - uPointerDiff; 25 | // Set `color` to black 26 | vec3 color = vec3(0.0); 27 | // If it is a grid line, change blue channel to 0.3 28 | color.b = isGridLine(coord) * 0.3; 29 | // Assing the final rgba color to `gl_FragColor` 30 | gl_FragColor = vec4(color, 1.0); 31 | } 32 | -------------------------------------------------------------------------------- /shaders/backgroundFragment.glsl: -------------------------------------------------------------------------------- 1 | // Adapted from: https://www.shadertoy.com/view/MdlGRr 2 | 3 | // It is required to set the float precision for fragment shaders in OpenGL ES 4 | // More info here: https://stackoverflow.com/a/28540641/4908989 5 | #ifdef GL_ES 6 | precision mediump float; 7 | #endif 8 | 9 | uniform vec2 uPointerDiff; 10 | 11 | // This function returns 1 if `coord` correspond to a grid line, 0 otherwise 12 | float isGridLine (vec2 coord) { 13 | vec2 pixelsPerGrid = vec2(50.0, 50.0); 14 | vec2 gridCoords = fract(coord / pixelsPerGrid); 15 | vec2 gridPixelCoords = gridCoords * pixelsPerGrid; 16 | vec2 gridLine = step(gridPixelCoords, vec2(1.0)); 17 | float isGridLine = max(gridLine.x, gridLine.y); 18 | return isGridLine; 19 | } 20 | 21 | // Main function 22 | void main () { 23 | // Coordinates minus the `uPointerDiff` value, and plus an offset 24 | vec2 coord = gl_FragCoord.xy - uPointerDiff + vec2(10.0); 25 | // Set `color` to black 26 | vec3 color = vec3(0.0); 27 | // If it is a grid line, change blue channel to 0.3 28 | color.b = isGridLine(coord) * 0.3; 29 | // Assing the final rgba color to `gl_FragColor` 30 | gl_FragColor = vec4(color, 1.0); 31 | } 32 | -------------------------------------------------------------------------------- /demos/final/shaders/backgroundFragment.glsl: -------------------------------------------------------------------------------- 1 | // Adapted from: https://www.shadertoy.com/view/MdlGRr 2 | 3 | // It is required to set the float precision for fragment shaders in OpenGL ES 4 | // More info here: https://stackoverflow.com/a/28540641/4908989 5 | #ifdef GL_ES 6 | precision mediump float; 7 | #endif 8 | 9 | uniform vec2 uPointerDiff; 10 | 11 | // This function returns 1 if `coord` correspond to a grid line, 0 otherwise 12 | float isGridLine (vec2 coord) { 13 | vec2 pixelsPerGrid = vec2(50.0, 50.0); 14 | vec2 gridCoords = fract(coord / pixelsPerGrid); 15 | vec2 gridPixelCoords = gridCoords * pixelsPerGrid; 16 | vec2 gridLine = step(gridPixelCoords, vec2(1.0)); 17 | float isGridLine = max(gridLine.x, gridLine.y); 18 | return isGridLine; 19 | } 20 | 21 | // Main function 22 | void main () { 23 | // Coordinates minus the `uPointerDiff` value, and plus an offset 24 | vec2 coord = gl_FragCoord.xy - uPointerDiff + vec2(10.0); 25 | // Set `color` to black 26 | vec3 color = vec3(0.0); 27 | // If it is a grid line, change blue channel to 0.3 28 | color.b = isGridLine(coord) * 0.3; 29 | // Assing the final rgba color to `gl_FragColor` 30 | gl_FragColor = vec4(color, 1.0); 31 | } 32 | -------------------------------------------------------------------------------- /demos/images/shaders/backgroundFragment.glsl: -------------------------------------------------------------------------------- 1 | // Adapted from: https://www.shadertoy.com/view/MdlGRr 2 | 3 | // It is required to set the float precision for fragment shaders in OpenGL ES 4 | // More info here: https://stackoverflow.com/a/28540641/4908989 5 | #ifdef GL_ES 6 | precision mediump float; 7 | #endif 8 | 9 | uniform vec2 uPointerDiff; 10 | 11 | // This function returns 1 if `coord` correspond to a grid line, 0 otherwise 12 | float isGridLine (vec2 coord) { 13 | vec2 pixelsPerGrid = vec2(50.0, 50.0); 14 | vec2 gridCoords = fract(coord / pixelsPerGrid); 15 | vec2 gridPixelCoords = gridCoords * pixelsPerGrid; 16 | vec2 gridLine = step(gridPixelCoords, vec2(1.0)); 17 | float isGridLine = max(gridLine.x, gridLine.y); 18 | return isGridLine; 19 | } 20 | 21 | // Main function 22 | void main () { 23 | // Coordinates minus the `uPointerDiff` value, and plus an offset 24 | vec2 coord = gl_FragCoord.xy - uPointerDiff + vec2(10.0); 25 | // Set `color` to black 26 | vec3 color = vec3(0.0); 27 | // If it is a grid line, change blue channel to 0.3 28 | color.b = isGridLine(coord) * 0.3; 29 | // Assing the final rgba color to `gl_FragColor` 30 | gl_FragColor = vec4(color, 1.0); 31 | } 32 | -------------------------------------------------------------------------------- /demos/rects-final/shaders/backgroundFragment.glsl: -------------------------------------------------------------------------------- 1 | // Adapted from: https://www.shadertoy.com/view/MdlGRr 2 | 3 | // It is required to set the float precision for fragment shaders in OpenGL ES 4 | // More info here: https://stackoverflow.com/a/28540641/4908989 5 | #ifdef GL_ES 6 | precision mediump float; 7 | #endif 8 | 9 | uniform vec2 uPointerDiff; 10 | 11 | // This function returns 1 if `coord` correspond to a grid line, 0 otherwise 12 | float isGridLine (vec2 coord) { 13 | vec2 pixelsPerGrid = vec2(50.0, 50.0); 14 | vec2 gridCoords = fract(coord / pixelsPerGrid); 15 | vec2 gridPixelCoords = gridCoords * pixelsPerGrid; 16 | vec2 gridLine = step(gridPixelCoords, vec2(1.0)); 17 | float isGridLine = max(gridLine.x, gridLine.y); 18 | return isGridLine; 19 | } 20 | 21 | // Main function 22 | void main () { 23 | // Coordinates minus the `uPointerDiff` value, and plus an offset 24 | vec2 coord = gl_FragCoord.xy - uPointerDiff + vec2(10.0); 25 | // Set `color` to black 26 | vec3 color = vec3(0.0); 27 | // If it is a grid line, change blue channel to 0.3 28 | color.b = isGridLine(coord) * 0.3; 29 | // Assing the final rgba color to `gl_FragColor` 30 | gl_FragColor = vec4(color, 1.0); 31 | } 32 | -------------------------------------------------------------------------------- /shaders/stageFragment.glsl: -------------------------------------------------------------------------------- 1 | // Adapted from: https://www.shadertoy.com/view/4lSGRw 2 | 3 | #ifdef GL_ES 4 | precision mediump float; 5 | #endif 6 | 7 | // Uniforms from Javascript 8 | uniform vec2 uResolution; 9 | uniform float uPointerDown; 10 | 11 | // The texture is defined by PixiJS 12 | varying vec2 vTextureCoord; 13 | uniform sampler2D uSampler; 14 | 15 | // Function used to get the distortion effect 16 | vec2 computeUV (vec2 uv, float k, float kcube) { 17 | vec2 t = uv - 0.5; 18 | float r2 = t.x * t.x + t.y * t.y; 19 | float f = 0.0; 20 | if (kcube == 0.0) { 21 | f = 1.0 + r2 * k; 22 | } else { 23 | f = 1.0 + r2 * (k + kcube * sqrt(r2)); 24 | } 25 | vec2 nUv = f * t + 0.5; 26 | nUv.y = 1.0 - nUv.y; 27 | return nUv; 28 | } 29 | 30 | void main () { 31 | // Normalized coordinates 32 | vec2 uv = gl_FragCoord.xy / uResolution.xy; 33 | 34 | // Settings for the effect 35 | // Multiplied by `uPointerDown`, a value between 0 and 1 36 | float k = -1.0 * uPointerDown; 37 | float kcube = 0.5 * uPointerDown; 38 | float offset = 0.02 * uPointerDown; 39 | 40 | // Get each channel's color using the texture provided by PixiJS 41 | // and the `computeUV` function 42 | float red = texture2D(uSampler, computeUV(uv, k + offset, kcube)).r; 43 | float green = texture2D(uSampler, computeUV(uv, k, kcube)).g; 44 | float blue = texture2D(uSampler, computeUV(uv, k - offset, kcube)).b; 45 | 46 | // Assing the final rgba color to `gl_FragColor` 47 | gl_FragColor = vec4(red, green, blue, 1.0); 48 | } 49 | -------------------------------------------------------------------------------- /demos/events/shaders/stageFragment.glsl: -------------------------------------------------------------------------------- 1 | // Adapted from: https://www.shadertoy.com/view/4lSGRw 2 | 3 | #ifdef GL_ES 4 | precision mediump float; 5 | #endif 6 | 7 | // Uniforms from Javascript 8 | uniform vec2 uResolution; 9 | uniform float uPointerDown; 10 | 11 | // The texture is defined by PixiJS 12 | varying vec2 vTextureCoord; 13 | uniform sampler2D uSampler; 14 | 15 | // Function used to get the distortion effect 16 | vec2 computeUV (vec2 uv, float k, float kcube) { 17 | vec2 t = uv - 0.5; 18 | float r2 = t.x * t.x + t.y * t.y; 19 | float f = 0.0; 20 | if (kcube == 0.0) { 21 | f = 1.0 + r2 * k; 22 | } else { 23 | f = 1.0 + r2 * (k + kcube * sqrt(r2)); 24 | } 25 | vec2 nUv = f * t + 0.5; 26 | nUv.y = 1.0 - nUv.y; 27 | return nUv; 28 | } 29 | 30 | void main () { 31 | // Normalized coordinates 32 | vec2 uv = gl_FragCoord.xy / uResolution.xy; 33 | 34 | // Settings for the effect 35 | // Multiplied by `uPointerDown`, a value between 0 and 1 36 | float k = -1.0 * uPointerDown; 37 | float kcube = 0.5 * uPointerDown; 38 | float offset = 0.02 * uPointerDown; 39 | 40 | // Get each channel's color using the texture provided by PixiJS 41 | // and the `computeUV` function 42 | float red = texture2D(uSampler, computeUV(uv, k + offset, kcube)).r; 43 | float green = texture2D(uSampler, computeUV(uv, k, kcube)).g; 44 | float blue = texture2D(uSampler, computeUV(uv, k - offset, kcube)).b; 45 | 46 | // Assing the final rgba color to `gl_FragColor` 47 | gl_FragColor = vec4(red, green, blue, 1.0); 48 | } 49 | -------------------------------------------------------------------------------- /demos/final/shaders/stageFragment.glsl: -------------------------------------------------------------------------------- 1 | // Adapted from: https://www.shadertoy.com/view/4lSGRw 2 | 3 | #ifdef GL_ES 4 | precision mediump float; 5 | #endif 6 | 7 | // Uniforms from Javascript 8 | uniform vec2 uResolution; 9 | uniform float uPointerDown; 10 | 11 | // The texture is defined by PixiJS 12 | varying vec2 vTextureCoord; 13 | uniform sampler2D uSampler; 14 | 15 | // Function used to get the distortion effect 16 | vec2 computeUV (vec2 uv, float k, float kcube) { 17 | vec2 t = uv - 0.5; 18 | float r2 = t.x * t.x + t.y * t.y; 19 | float f = 0.0; 20 | if (kcube == 0.0) { 21 | f = 1.0 + r2 * k; 22 | } else { 23 | f = 1.0 + r2 * (k + kcube * sqrt(r2)); 24 | } 25 | vec2 nUv = f * t + 0.5; 26 | nUv.y = 1.0 - nUv.y; 27 | return nUv; 28 | } 29 | 30 | void main () { 31 | // Normalized coordinates 32 | vec2 uv = gl_FragCoord.xy / uResolution.xy; 33 | 34 | // Settings for the effect 35 | // Multiplied by `uPointerDown`, a value between 0 and 1 36 | float k = -1.0 * uPointerDown; 37 | float kcube = 0.5 * uPointerDown; 38 | float offset = 0.02 * uPointerDown; 39 | 40 | // Get each channel's color using the texture provided by PixiJS 41 | // and the `computeUV` function 42 | float red = texture2D(uSampler, computeUV(uv, k + offset, kcube)).r; 43 | float green = texture2D(uSampler, computeUV(uv, k, kcube)).g; 44 | float blue = texture2D(uSampler, computeUV(uv, k - offset, kcube)).b; 45 | 46 | // Assing the final rgba color to `gl_FragColor` 47 | gl_FragColor = vec4(red, green, blue, 1.0); 48 | } 49 | -------------------------------------------------------------------------------- /demos/images/shaders/stageFragment.glsl: -------------------------------------------------------------------------------- 1 | // Adapted from: https://www.shadertoy.com/view/4lSGRw 2 | 3 | #ifdef GL_ES 4 | precision mediump float; 5 | #endif 6 | 7 | // Uniforms from Javascript 8 | uniform vec2 uResolution; 9 | uniform float uPointerDown; 10 | 11 | // The texture is defined by PixiJS 12 | varying vec2 vTextureCoord; 13 | uniform sampler2D uSampler; 14 | 15 | // Function used to get the distortion effect 16 | vec2 computeUV (vec2 uv, float k, float kcube) { 17 | vec2 t = uv - 0.5; 18 | float r2 = t.x * t.x + t.y * t.y; 19 | float f = 0.0; 20 | if (kcube == 0.0) { 21 | f = 1.0 + r2 * k; 22 | } else { 23 | f = 1.0 + r2 * (k + kcube * sqrt(r2)); 24 | } 25 | vec2 nUv = f * t + 0.5; 26 | nUv.y = 1.0 - nUv.y; 27 | return nUv; 28 | } 29 | 30 | void main () { 31 | // Normalized coordinates 32 | vec2 uv = gl_FragCoord.xy / uResolution.xy; 33 | 34 | // Settings for the effect 35 | // Multiplied by `uPointerDown`, a value between 0 and 1 36 | float k = -1.0 * uPointerDown; 37 | float kcube = 0.5 * uPointerDown; 38 | float offset = 0.02 * uPointerDown; 39 | 40 | // Get each channel's color using the texture provided by PixiJS 41 | // and the `computeUV` function 42 | float red = texture2D(uSampler, computeUV(uv, k + offset, kcube)).r; 43 | float green = texture2D(uSampler, computeUV(uv, k, kcube)).g; 44 | float blue = texture2D(uSampler, computeUV(uv, k - offset, kcube)).b; 45 | 46 | // Assing the final rgba color to `gl_FragColor` 47 | gl_FragColor = vec4(red, green, blue, 1.0); 48 | } 49 | -------------------------------------------------------------------------------- /demos/animation/shaders/stageFragment.glsl: -------------------------------------------------------------------------------- 1 | // Adapted from: https://www.shadertoy.com/view/4lSGRw 2 | 3 | #ifdef GL_ES 4 | precision mediump float; 5 | #endif 6 | 7 | // Uniforms from Javascript 8 | uniform vec2 uResolution; 9 | uniform float uPointerDown; 10 | 11 | // The texture is defined by PixiJS 12 | varying vec2 vTextureCoord; 13 | uniform sampler2D uSampler; 14 | 15 | // Function used to get the distortion effect 16 | vec2 computeUV (vec2 uv, float k, float kcube) { 17 | vec2 t = uv - 0.5; 18 | float r2 = t.x * t.x + t.y * t.y; 19 | float f = 0.0; 20 | if (kcube == 0.0) { 21 | f = 1.0 + r2 * k; 22 | } else { 23 | f = 1.0 + r2 * (k + kcube * sqrt(r2)); 24 | } 25 | vec2 nUv = f * t + 0.5; 26 | nUv.y = 1.0 - nUv.y; 27 | return nUv; 28 | } 29 | 30 | void main () { 31 | // Normalized coordinates 32 | vec2 uv = gl_FragCoord.xy / uResolution.xy; 33 | 34 | // Settings for the effect 35 | // Multiplied by `uPointerDown`, a value between 0 and 1 36 | float k = -1.0 * uPointerDown; 37 | float kcube = 0.5 * uPointerDown; 38 | float offset = 0.02 * uPointerDown; 39 | 40 | // Get each channel's color using the texture provided by PixiJS 41 | // and the `computeUV` function 42 | float red = texture2D(uSampler, computeUV(uv, k + offset, kcube)).r; 43 | float green = texture2D(uSampler, computeUV(uv, k, kcube)).g; 44 | float blue = texture2D(uSampler, computeUV(uv, k - offset, kcube)).b; 45 | 46 | // Assing the final rgba color to `gl_FragColor` 47 | gl_FragColor = vec4(red, green, blue, 1.0); 48 | } 49 | -------------------------------------------------------------------------------- /demos/distortion/shaders/stageFragment.glsl: -------------------------------------------------------------------------------- 1 | // Adapted from: https://www.shadertoy.com/view/4lSGRw 2 | 3 | #ifdef GL_ES 4 | precision mediump float; 5 | #endif 6 | 7 | // Uniforms from Javascript 8 | uniform vec2 uResolution; 9 | uniform float uPointerDown; 10 | 11 | // The texture is defined by PixiJS 12 | varying vec2 vTextureCoord; 13 | uniform sampler2D uSampler; 14 | 15 | // Function used to get the distortion effect 16 | vec2 computeUV (vec2 uv, float k, float kcube) { 17 | vec2 t = uv - 0.5; 18 | float r2 = t.x * t.x + t.y * t.y; 19 | float f = 0.0; 20 | if (kcube == 0.0) { 21 | f = 1.0 + r2 * k; 22 | } else { 23 | f = 1.0 + r2 * (k + kcube * sqrt(r2)); 24 | } 25 | vec2 nUv = f * t + 0.5; 26 | nUv.y = 1.0 - nUv.y; 27 | return nUv; 28 | } 29 | 30 | void main () { 31 | // Normalized coordinates 32 | vec2 uv = gl_FragCoord.xy / uResolution.xy; 33 | 34 | // Settings for the effect 35 | // Multiplied by `uPointerDown`, a value between 0 and 1 36 | float k = -1.0 * uPointerDown; 37 | float kcube = 0.5 * uPointerDown; 38 | float offset = 0.02 * uPointerDown; 39 | 40 | // Get each channel's color using the texture provided by PixiJS 41 | // and the `computeUV` function 42 | float red = texture2D(uSampler, computeUV(uv, k + offset, kcube)).r; 43 | float green = texture2D(uSampler, computeUV(uv, k, kcube)).g; 44 | float blue = texture2D(uSampler, computeUV(uv, k - offset, kcube)).b; 45 | 46 | // Assing the final rgba color to `gl_FragColor` 47 | gl_FragColor = vec4(red, green, blue, 1.0); 48 | } 49 | -------------------------------------------------------------------------------- /demos/rects-final/shaders/stageFragment.glsl: -------------------------------------------------------------------------------- 1 | // Adapted from: https://www.shadertoy.com/view/4lSGRw 2 | 3 | #ifdef GL_ES 4 | precision mediump float; 5 | #endif 6 | 7 | // Uniforms from Javascript 8 | uniform vec2 uResolution; 9 | uniform float uPointerDown; 10 | 11 | // The texture is defined by PixiJS 12 | varying vec2 vTextureCoord; 13 | uniform sampler2D uSampler; 14 | 15 | // Function used to get the distortion effect 16 | vec2 computeUV (vec2 uv, float k, float kcube) { 17 | vec2 t = uv - 0.5; 18 | float r2 = t.x * t.x + t.y * t.y; 19 | float f = 0.0; 20 | if (kcube == 0.0) { 21 | f = 1.0 + r2 * k; 22 | } else { 23 | f = 1.0 + r2 * (k + kcube * sqrt(r2)); 24 | } 25 | vec2 nUv = f * t + 0.5; 26 | nUv.y = 1.0 - nUv.y; 27 | return nUv; 28 | } 29 | 30 | void main () { 31 | // Normalized coordinates 32 | vec2 uv = gl_FragCoord.xy / uResolution.xy; 33 | 34 | // Settings for the effect 35 | // Multiplied by `uPointerDown`, a value between 0 and 1 36 | float k = -1.0 * uPointerDown; 37 | float kcube = 0.5 * uPointerDown; 38 | float offset = 0.02 * uPointerDown; 39 | 40 | // Get each channel's color using the texture provided by PixiJS 41 | // and the `computeUV` function 42 | float red = texture2D(uSampler, computeUV(uv, k + offset, kcube)).r; 43 | float green = texture2D(uSampler, computeUV(uv, k, kcube)).g; 44 | float blue = texture2D(uSampler, computeUV(uv, k - offset, kcube)).b; 45 | 46 | // Assing the final rgba color to `gl_FragColor` 47 | gl_FragColor = vec4(red, green, blue, 1.0); 48 | } 49 | -------------------------------------------------------------------------------- /demos/rects-pixi/shaders/stageFragment.glsl: -------------------------------------------------------------------------------- 1 | // Adapted from: https://www.shadertoy.com/view/4lSGRw 2 | 3 | #ifdef GL_ES 4 | precision mediump float; 5 | #endif 6 | 7 | // Uniforms from Javascript 8 | uniform vec2 uResolution; 9 | uniform float uPointerDown; 10 | 11 | // The texture is defined by PixiJS 12 | varying vec2 vTextureCoord; 13 | uniform sampler2D uSampler; 14 | 15 | // Function used to get the distortion effect 16 | vec2 computeUV (vec2 uv, float k, float kcube) { 17 | vec2 t = uv - 0.5; 18 | float r2 = t.x * t.x + t.y * t.y; 19 | float f = 0.0; 20 | if (kcube == 0.0) { 21 | f = 1.0 + r2 * k; 22 | } else { 23 | f = 1.0 + r2 * (k + kcube * sqrt(r2)); 24 | } 25 | vec2 nUv = f * t + 0.5; 26 | nUv.y = 1.0 - nUv.y; 27 | return nUv; 28 | } 29 | 30 | void main () { 31 | // Normalized coordinates 32 | vec2 uv = gl_FragCoord.xy / uResolution.xy; 33 | 34 | // Settings for the effect 35 | // Multiplied by `uPointerDown`, a value between 0 and 1 36 | float k = -1.0 * uPointerDown; 37 | float kcube = 0.5 * uPointerDown; 38 | float offset = 0.02 * uPointerDown; 39 | 40 | // Get each channel's color using the texture provided by PixiJS 41 | // and the `computeUV` function 42 | float red = texture2D(uSampler, computeUV(uv, k + offset, kcube)).r; 43 | float green = texture2D(uSampler, computeUV(uv, k, kcube)).g; 44 | float blue = texture2D(uSampler, computeUV(uv, k - offset, kcube)).b; 45 | 46 | // Assing the final rgba color to `gl_FragColor` 47 | gl_FragColor = vec4(red, green, blue, 1.0); 48 | } 49 | -------------------------------------------------------------------------------- /demos/background/js/scripts.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Get canvas view 4 | const view = document.querySelector('.view') 5 | // Loaded resources will be here 6 | const resources = PIXI.Loader.shared.resources 7 | let width, height, app, background 8 | 9 | // Set dimensions 10 | function initDimensions () { 11 | width = window.innerWidth 12 | height = window.innerHeight 13 | } 14 | 15 | // Init the PixiJS Application 16 | function initApp () { 17 | // Create a PixiJS Application, using the view (canvas) provided 18 | app = new PIXI.Application({ view }) 19 | // Resizes renderer view in CSS pixels to allow for resolutions other than 1 20 | app.renderer.autoDensity = true 21 | // Resize the view to match viewport dimensions 22 | app.renderer.resize(width, height) 23 | } 24 | 25 | // Init the gridded background 26 | function initBackground () { 27 | // Create a new empty Sprite and define its size 28 | background = new PIXI.Sprite() 29 | background.width = width 30 | background.height = height 31 | // Get the code for the fragment shader from the loaded resources 32 | const backgroundFragmentShader = resources['shaders/backgroundFragment.glsl'].data 33 | // Create a new Filter using the fragment shader 34 | // We don't need a custom vertex shader, so we set it as `undefined` 35 | const backgroundFilter = new PIXI.Filter(undefined, backgroundFragmentShader) 36 | // Assign the filter to the background Sprite 37 | background.filters = [backgroundFilter] 38 | // Add the background to the stage 39 | app.stage.addChild(background) 40 | } 41 | 42 | // Init everything 43 | function init () { 44 | initDimensions() 45 | initApp() 46 | initBackground() 47 | } 48 | 49 | // Load resources, then init the app 50 | PIXI.Loader.shared.add([ 51 | 'shaders/backgroundFragment.glsl' 52 | ]).load(init) 53 | 54 | })() 55 | -------------------------------------------------------------------------------- /demos/distortion/js/scripts.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Get canvas view 4 | const view = document.querySelector('.view') 5 | // Loaded resources will be here 6 | const resources = PIXI.Loader.shared.resources 7 | // Target for pointer. If down, value is 1, else value is 0 8 | // Here we set it to 1 to see the effect, but initially it will be 0 9 | let pointerDownTarget = 1 10 | let width, height, app, background, uniforms 11 | 12 | // Set dimensions 13 | function initDimensions () { 14 | width = window.innerWidth 15 | height = window.innerHeight 16 | } 17 | 18 | // Set initial values for uniforms 19 | function initUniforms () { 20 | uniforms = { 21 | uResolution: new PIXI.Point(width, height), 22 | uPointerDown: pointerDownTarget 23 | } 24 | } 25 | 26 | // Init the PixiJS Application 27 | function initApp () { 28 | // Create a PixiJS Application, using the view (canvas) provided 29 | app = new PIXI.Application({ view }) 30 | // Resizes renderer view in CSS pixels to allow for resolutions other than 1 31 | app.renderer.autoDensity = true 32 | // Resize the view to match viewport dimensions 33 | app.renderer.resize(width, height) 34 | 35 | // Set the distortion filter for the entire stage 36 | const stageFragmentShader = resources['shaders/stageFragment.glsl'].data 37 | const stageFilter = new PIXI.Filter(undefined, stageFragmentShader, uniforms) 38 | app.stage.filters = [stageFilter] 39 | } 40 | 41 | // Init the gridded background 42 | function initBackground () { 43 | // Create a new empty Sprite and define its size 44 | background = new PIXI.Sprite() 45 | background.width = width 46 | background.height = height 47 | // Get the code for the fragment shader from the loaded resources 48 | const backgroundFragmentShader = resources['shaders/backgroundFragment.glsl'].data 49 | // Create a new Filter using the fragment shader 50 | // We don't need a custom vertex shader, so we set it as `undefined` 51 | const backgroundFilter = new PIXI.Filter(undefined, backgroundFragmentShader) 52 | // Assign the filter to the background Sprite 53 | background.filters = [backgroundFilter] 54 | // Add the background to the stage 55 | app.stage.addChild(background) 56 | } 57 | 58 | // Init everything 59 | function init () { 60 | initDimensions() 61 | initUniforms() 62 | initApp() 63 | initBackground() 64 | } 65 | 66 | // Load resources, then init the app 67 | PIXI.Loader.shared.add([ 68 | 'shaders/stageFragment.glsl', 69 | 'shaders/backgroundFragment.glsl' 70 | ]).load(init) 71 | 72 | })() 73 | -------------------------------------------------------------------------------- /demos/events/js/scripts.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Get canvas view 4 | const view = document.querySelector('.view') 5 | // Loaded resources will be here 6 | const resources = PIXI.Loader.shared.resources 7 | // Target for pointer. If down, value is 1, else value is 0 8 | let pointerDownTarget = 0 9 | // Useful variables to keep track of the pointer 10 | let pointerStart = new PIXI.Point() 11 | let pointerDiffStart = new PIXI.Point() 12 | let width, height, app, background, uniforms, diffX, diffY 13 | 14 | // Set dimensions 15 | function initDimensions () { 16 | width = window.innerWidth 17 | height = window.innerHeight 18 | } 19 | 20 | // Set initial values for uniforms 21 | function initUniforms () { 22 | uniforms = { 23 | uResolution: new PIXI.Point(width, height), 24 | uPointerDiff: new PIXI.Point(), 25 | uPointerDown: pointerDownTarget 26 | } 27 | } 28 | 29 | // Init the PixiJS Application 30 | function initApp () { 31 | // Create a PixiJS Application, using the view (canvas) provided 32 | app = new PIXI.Application({ view }) 33 | // Resizes renderer view in CSS pixels to allow for resolutions other than 1 34 | app.renderer.autoDensity = true 35 | // Resize the view to match viewport dimensions 36 | app.renderer.resize(width, height) 37 | 38 | // Set the distortion filter for the entire stage 39 | const stageFragmentShader = resources['shaders/stageFragment.glsl'].data 40 | const stageFilter = new PIXI.Filter(undefined, stageFragmentShader, uniforms) 41 | app.stage.filters = [stageFilter] 42 | } 43 | 44 | // Init the gridded background 45 | function initBackground () { 46 | // Create a new empty Sprite and define its size 47 | background = new PIXI.Sprite() 48 | background.width = width 49 | background.height = height 50 | // Get the code for the fragment shader from the loaded resources 51 | const backgroundFragmentShader = resources['shaders/backgroundFragment.glsl'].data 52 | // Create a new Filter using the fragment shader 53 | // We don't need a custom vertex shader, so we set it as `undefined` 54 | const backgroundFilter = new PIXI.Filter(undefined, backgroundFragmentShader) 55 | // Assign the filter to the background Sprite 56 | background.filters = [backgroundFilter] 57 | // Add the background to the stage 58 | app.stage.addChild(background) 59 | } 60 | 61 | // Start listening events 62 | function initEvents () { 63 | // Make stage interactive, so it can listen to events 64 | app.stage.interactive = true 65 | 66 | // Pointer & touch events are normalized into 67 | // the `pointer*` events for handling different events 68 | app.stage 69 | .on('pointerdown', onPointerDown) 70 | .on('pointerup', onPointerUp) 71 | .on('pointerupoutside', onPointerUp) 72 | .on('pointermove', onPointerMove) 73 | } 74 | 75 | // On pointer down, save coordinates and set pointerDownTarget 76 | function onPointerDown (e) { 77 | console.log('down') 78 | const { x, y } = e.data.global 79 | pointerDownTarget = 1 80 | pointerStart.set(x, y) 81 | pointerDiffStart = uniforms.uPointerDiff.clone() 82 | } 83 | 84 | // On pointer up, set pointerDownTarget 85 | function onPointerUp () { 86 | console.log('up') 87 | pointerDownTarget = 0 88 | } 89 | 90 | // On pointer move, calculate coordinates diff 91 | function onPointerMove (e) { 92 | const { x, y } = e.data.global 93 | if (pointerDownTarget) { 94 | console.log('dragging') 95 | diffX = pointerDiffStart.x + (x - pointerStart.x) 96 | diffY = pointerDiffStart.y + (y - pointerStart.y) 97 | } 98 | } 99 | 100 | // Init everything 101 | function init () { 102 | initDimensions() 103 | initUniforms() 104 | initApp() 105 | initBackground() 106 | initEvents() 107 | } 108 | 109 | // Load resources, then init the app 110 | PIXI.Loader.shared.add([ 111 | 'shaders/stageFragment.glsl', 112 | 'shaders/backgroundFragment.glsl' 113 | ]).load(init) 114 | 115 | })() 116 | -------------------------------------------------------------------------------- /demos/rects/js/scripts.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Class to generate a random masonry layout, using a square grid as base 4 | class Grid { 5 | 6 | // The constructor receives all the following parameters: 7 | // - gridSize: The size (width and height) for smallest unit size 8 | // - gridColumns: Number of columns for the grid (width = gridColumns * gridSize) 9 | // - gridRows: Number of rows for the grid (height = gridRows * gridSize) 10 | // - gridMin: Min width and height limits for rectangles (in grid units) 11 | constructor(gridSize, gridColumns, gridRows, gridMin) { 12 | this.gridSize = gridSize 13 | this.gridColumns = gridColumns 14 | this.gridRows = gridRows 15 | this.gridMin = gridMin 16 | this.rects = [] 17 | this.currentRects = [{ x: 0, y: 0, w: this.gridColumns, h: this.gridRows }] 18 | } 19 | 20 | // Takes the first rectangle on the list, and divides it in 2 more rectangles if possible 21 | splitCurrentRect () { 22 | if (this.currentRects.length) { 23 | const currentRect = this.currentRects.shift() 24 | const cutVertical = currentRect.w > currentRect.h 25 | const cutSide = cutVertical ? currentRect.w : currentRect.h 26 | const cutSize = cutVertical ? 'w' : 'h' 27 | const cutAxis = cutVertical ? 'x' : 'y' 28 | if (cutSide > this.gridMin * 2) { 29 | const rect1Size = randomInRange(this.gridMin, cutSide - this.gridMin) 30 | const rect1 = Object.assign({}, currentRect, { [cutSize]: rect1Size }) 31 | const rect2 = Object.assign({}, currentRect, { [cutAxis]: currentRect[cutAxis] + rect1Size, [cutSize]: currentRect[cutSize] - rect1Size }) 32 | drawRect(rect1) 33 | drawRect(rect2) 34 | this.currentRects.push(rect1, rect2) 35 | } 36 | else { 37 | this.rects.push(currentRect) 38 | this.splitCurrentRect() 39 | } 40 | } else { 41 | console.log('end') 42 | } 43 | } 44 | 45 | // Call `splitCurrentRect` until there is no more rectangles on the list 46 | // Then return the list of rectangles 47 | generateRects () { 48 | while (this.currentRects.length) { 49 | this.splitCurrentRect() 50 | } 51 | return this.rects 52 | } 53 | } 54 | 55 | // Generate a random integer in the range provided 56 | function randomInRange (min, max) { 57 | return Math.floor(Math.random() * (max - min + 1)) + min 58 | } 59 | 60 | // Generate a random color 61 | function randomColor () { 62 | const color = [0, 0, 0] 63 | for (let i = 0; i <= 2; i++) { 64 | if (Math.random() < 0.66666) 65 | color[i] = 32 + parseInt(Math.random() * 192) 66 | } 67 | return 'rgb(' + color[0] + ',' + color[1] + ',' + color[2] + ')' 68 | } 69 | 70 | // Draw a rect in the canvas 71 | function drawRect (r) { 72 | ctx.fillStyle = r.color || randomColor() 73 | ctx.fillRect(r.x * gridSize, r.y * gridSize, r.w * gridSize, r.h * gridSize) 74 | } 75 | 76 | // Clear the canvas 77 | function clearCanvas () { 78 | ctx.fillStyle = 'white' 79 | ctx.fillRect(0, 0, width, height) 80 | } 81 | 82 | // Variables and settings 83 | const next = document.getElementById('next') 84 | const all = document.getElementById('all') 85 | const reset = document.getElementById('reset') 86 | const canvas = document.getElementById('canvas') 87 | const ctx = canvas.getContext('2d') 88 | const gridSize = 50 89 | const gridColumns = 20 90 | const gridRows = 10 91 | const gridMin = 3 92 | const width = gridSize * gridColumns 93 | const height = gridSize * gridRows 94 | canvas.width = width 95 | canvas.height = height 96 | 97 | // Start a new grid 98 | let grid 99 | function start () { 100 | grid = new Grid(gridSize, gridColumns, gridRows, gridMin) 101 | } 102 | start() 103 | 104 | // Listen for click on buttons and run the algoritm 105 | 106 | next.addEventListener('click', () => { 107 | grid.splitCurrentRect() 108 | }) 109 | 110 | all.addEventListener('click', () => { 111 | grid.generateRects() 112 | }) 113 | 114 | reset.addEventListener('click', () => { 115 | clearCanvas() 116 | start() 117 | }) 118 | 119 | })() 120 | -------------------------------------------------------------------------------- /demos/animation/js/scripts.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Get canvas view 4 | const view = document.querySelector('.view') 5 | // Loaded resources will be here 6 | const resources = PIXI.Loader.shared.resources 7 | // Target for pointer. If down, value is 1, else value is 0 8 | let pointerDownTarget = 0 9 | // Useful variables to keep track of the pointer 10 | let pointerStart = new PIXI.Point() 11 | let pointerDiffStart = new PIXI.Point() 12 | let width, height, app, background, uniforms, diffX, diffY 13 | 14 | // Set dimensions 15 | function initDimensions () { 16 | width = window.innerWidth 17 | height = window.innerHeight 18 | diffX = 0 19 | diffY = 0 20 | } 21 | 22 | // Set initial values for uniforms 23 | function initUniforms () { 24 | uniforms = { 25 | uResolution: new PIXI.Point(width, height), 26 | uPointerDiff: new PIXI.Point(), 27 | uPointerDown: pointerDownTarget 28 | } 29 | } 30 | 31 | // Init the PixiJS Application 32 | function initApp () { 33 | // Create a PixiJS Application, using the view (canvas) provided 34 | app = new PIXI.Application({ view }) 35 | // Resizes renderer view in CSS pixels to allow for resolutions other than 1 36 | app.renderer.autoDensity = true 37 | // Resize the view to match viewport dimensions 38 | app.renderer.resize(width, height) 39 | 40 | // Set the distortion filter for the entire stage 41 | const stageFragmentShader = resources['shaders/stageFragment.glsl'].data 42 | const stageFilter = new PIXI.Filter(undefined, stageFragmentShader, uniforms) 43 | app.stage.filters = [stageFilter] 44 | } 45 | 46 | // Init the gridded background 47 | function initBackground () { 48 | // Create a new empty Sprite and define its size 49 | background = new PIXI.Sprite() 50 | background.width = width 51 | background.height = height 52 | // Get the code for the fragment shader from the loaded resources 53 | const backgroundFragmentShader = resources['shaders/backgroundFragment.glsl'].data 54 | // Create a new Filter using the fragment shader 55 | // We don't need a custom vertex shader, so we set it as `undefined` 56 | const backgroundFilter = new PIXI.Filter(undefined, backgroundFragmentShader, uniforms) 57 | // Assign the filter to the background Sprite 58 | background.filters = [backgroundFilter] 59 | // Add the background to the stage 60 | app.stage.addChild(background) 61 | } 62 | 63 | // Start listening events 64 | function initEvents () { 65 | // Make stage interactive, so it can listen to events 66 | app.stage.interactive = true 67 | 68 | // Pointer & touch events are normalized into 69 | // the `pointer*` events for handling different events 70 | app.stage 71 | .on('pointerdown', onPointerDown) 72 | .on('pointerup', onPointerUp) 73 | .on('pointerupoutside', onPointerUp) 74 | .on('pointermove', onPointerMove) 75 | } 76 | 77 | // On pointer down, save coordinates and set pointerDownTarget 78 | function onPointerDown (e) { 79 | const { x, y } = e.data.global 80 | pointerDownTarget = 1 81 | pointerStart.set(x, y) 82 | pointerDiffStart = uniforms.uPointerDiff.clone() 83 | } 84 | 85 | // On pointer up, set pointerDownTarget 86 | function onPointerUp () { 87 | pointerDownTarget = 0 88 | } 89 | 90 | // On pointer move, calculate coordinates diff 91 | function onPointerMove (e) { 92 | const { x, y } = e.data.global 93 | if (pointerDownTarget) { 94 | diffX = pointerDiffStart.x + (x - pointerStart.x) 95 | diffY = pointerDiffStart.y + (y - pointerStart.y) 96 | } 97 | } 98 | 99 | // Init everything 100 | function init () { 101 | initDimensions() 102 | initUniforms() 103 | initApp() 104 | initBackground() 105 | initEvents() 106 | 107 | // Animation loop 108 | // Code here will be executed on every animation frame 109 | app.ticker.add(() => { 110 | // Multiply the values by a coefficient to get a smooth animation 111 | uniforms.uPointerDown += (pointerDownTarget - uniforms.uPointerDown) * 0.075 112 | uniforms.uPointerDiff.x += (diffX - uniforms.uPointerDiff.x) * 0.2 113 | uniforms.uPointerDiff.y += (diffY - uniforms.uPointerDiff.y) * 0.2 114 | }) 115 | } 116 | 117 | // Load resources, then init the app 118 | PIXI.Loader.shared.add([ 119 | 'shaders/stageFragment.glsl', 120 | 'shaders/backgroundFragment.glsl' 121 | ]).load(init) 122 | 123 | })() 124 | -------------------------------------------------------------------------------- /demos/rects-pixi/js/scripts.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Class to generate a random masonry layout, using a square grid as base 4 | class Grid { 5 | 6 | // The constructor receives all the following parameters: 7 | // - gridSize: The size (width and height) for smallest unit size 8 | // - gridColumns: Number of columns for the grid (width = gridColumns * gridSize) 9 | // - gridRows: Number of rows for the grid (height = gridRows * gridSize) 10 | // - gridMin: Min width and height limits for rectangles (in grid units) 11 | constructor(gridSize, gridColumns, gridRows, gridMin) { 12 | this.gridSize = gridSize 13 | this.gridColumns = gridColumns 14 | this.gridRows = gridRows 15 | this.gridMin = gridMin 16 | this.rects = [] 17 | this.currentRects = [{ x: 0, y: 0, w: this.gridColumns, h: this.gridRows }] 18 | } 19 | 20 | // Takes the first rectangle on the list, and divides it in 2 more rectangles if possible 21 | splitCurrentRect () { 22 | if (this.currentRects.length) { 23 | const currentRect = this.currentRects.shift() 24 | const cutVertical = currentRect.w > currentRect.h 25 | const cutSide = cutVertical ? currentRect.w : currentRect.h 26 | const cutSize = cutVertical ? 'w' : 'h' 27 | const cutAxis = cutVertical ? 'x' : 'y' 28 | if (cutSide > this.gridMin * 2) { 29 | const rect1Size = randomInRange(this.gridMin, cutSide - this.gridMin) 30 | const rect1 = Object.assign({}, currentRect, { [cutSize]: rect1Size }) 31 | const rect2 = Object.assign({}, currentRect, { [cutAxis]: currentRect[cutAxis] + rect1Size, [cutSize]: currentRect[cutSize] - rect1Size }) 32 | this.currentRects.push(rect1, rect2) 33 | } 34 | else { 35 | this.rects.push(currentRect) 36 | this.splitCurrentRect() 37 | } 38 | } 39 | } 40 | 41 | // Call `splitCurrentRect` until there is no more rectangles on the list 42 | // Then return the list of rectangles 43 | generateRects () { 44 | while (this.currentRects.length) { 45 | this.splitCurrentRect() 46 | } 47 | return this.rects 48 | } 49 | } 50 | 51 | // Generate a random integer in the range provided 52 | function randomInRange (min, max) { 53 | return Math.floor(Math.random() * (max - min + 1)) + min 54 | } 55 | 56 | // Get canvas view 57 | const view = document.querySelector('.view') 58 | // Loaded resources will be here 59 | const resources = PIXI.Loader.shared.resources 60 | // Target for pointer. If down, value is 1, else value is 0 61 | let pointerDownTarget = 0 62 | // Useful variables to keep track of the pointer 63 | let pointerStart = new PIXI.Point() 64 | let pointerDiffStart = new PIXI.Point() 65 | let width, height, app, background, uniforms, diffX, diffY 66 | 67 | // Variables and settings for grid 68 | const gridSize = 50 69 | const gridMin = 3 70 | const imagePadding = 20 71 | let gridColumnsCount, gridRowsCount, gridColumns, gridRows, grid 72 | let widthRest, heightRest, centerX, centerY, container, rects 73 | 74 | // Set dimensions 75 | function initDimensions () { 76 | width = window.innerWidth 77 | height = window.innerHeight 78 | diffX = 0 79 | diffY = 0 80 | } 81 | 82 | // Set initial values for uniforms 83 | function initUniforms () { 84 | uniforms = { 85 | uResolution: new PIXI.Point(width, height), 86 | uPointerDiff: new PIXI.Point(), 87 | uPointerDown: pointerDownTarget 88 | } 89 | } 90 | 91 | // Initialize the random grid layout 92 | function initGrid () { 93 | // Getting columns 94 | gridColumnsCount = Math.ceil(width / gridSize) 95 | // Getting rows 96 | gridRowsCount = Math.ceil(height / gridSize) 97 | // Make the grid 5 times bigger than viewport 98 | gridColumns = gridColumnsCount * 5 99 | gridRows = gridRowsCount * 5 100 | // Create a new Grid instance with our settings 101 | grid = new Grid(gridSize, gridColumns, gridRows, gridMin) 102 | // Calculate the center position for the grid in the viewport 103 | widthRest = Math.ceil(gridColumnsCount * gridSize - width) 104 | heightRest = Math.ceil(gridRowsCount * gridSize - height) 105 | centerX = (gridColumns * gridSize / 2) - (gridColumnsCount * gridSize / 2) 106 | centerY = (gridRows * gridSize / 2) - (gridRowsCount * gridSize / 2) 107 | // Generate the list of rects 108 | rects = grid.generateRects() 109 | } 110 | 111 | // Init the PixiJS Application 112 | function initApp () { 113 | // Create a PixiJS Application, using the view (canvas) provided 114 | app = new PIXI.Application({ view }) 115 | // Resizes renderer view in CSS pixels to allow for resolutions other than 1 116 | app.renderer.autoDensity = true 117 | // Resize the view to match viewport dimensions 118 | app.renderer.resize(width, height) 119 | 120 | // Set the distortion filter for the entire stage 121 | const stageFragmentShader = resources['shaders/stageFragment.glsl'].data 122 | const stageFilter = new PIXI.Filter(undefined, stageFragmentShader, uniforms) 123 | app.stage.filters = [stageFilter] 124 | } 125 | 126 | // Init the gridded background 127 | function initBackground () { 128 | // Create a new empty Sprite and define its size 129 | background = new PIXI.Sprite() 130 | background.width = width 131 | background.height = height 132 | // Get the code for the fragment shader from the loaded resources 133 | const backgroundFragmentShader = resources['shaders/backgroundFragment.glsl'].data 134 | // Create a new Filter using the fragment shader 135 | // We don't need a custom vertex shader, so we set it as `undefined` 136 | const backgroundFilter = new PIXI.Filter(undefined, backgroundFragmentShader, uniforms) 137 | // Assign the filter to the background Sprite 138 | background.filters = [backgroundFilter] 139 | // Add the background to the stage 140 | app.stage.addChild(background) 141 | } 142 | 143 | // Initialize a Container element for solid rectangles and images 144 | function initContainer () { 145 | container = new PIXI.Container() 146 | app.stage.addChild(container) 147 | } 148 | 149 | // Add solid rectangles and images 150 | // So far, we will only add rectangles 151 | function initRectsAndImages () { 152 | // Create a new Graphics element to draw solid rectangles 153 | const graphics = new PIXI.Graphics() 154 | // Select the color for rectangles 155 | graphics.beginFill(0xAA22CC) 156 | // Loop over each rect in the list 157 | rects.forEach(rect => { 158 | // Draw the rectangle 159 | graphics.drawRect( 160 | rect.x * gridSize, 161 | rect.y * gridSize, 162 | rect.w * gridSize - imagePadding, 163 | rect.h * gridSize - imagePadding 164 | ) 165 | }) 166 | // Ends the fill action 167 | graphics.endFill() 168 | // Add the graphics (with all drawn rects) to the container 169 | container.addChild(graphics) 170 | } 171 | 172 | // Start listening events 173 | function initEvents () { 174 | // Make stage interactive, so it can listen to events 175 | app.stage.interactive = true 176 | 177 | // Pointer & touch events are normalized into 178 | // the `pointer*` events for handling different events 179 | app.stage 180 | .on('pointerdown', onPointerDown) 181 | .on('pointerup', onPointerUp) 182 | .on('pointerupoutside', onPointerUp) 183 | .on('pointermove', onPointerMove) 184 | } 185 | 186 | // On pointer down, save coordinates and set pointerDownTarget 187 | function onPointerDown (e) { 188 | const { x, y } = e.data.global 189 | pointerDownTarget = 1 190 | pointerStart.set(x, y) 191 | pointerDiffStart = uniforms.uPointerDiff.clone() 192 | } 193 | 194 | // On pointer up, set pointerDownTarget 195 | function onPointerUp () { 196 | pointerDownTarget = 0 197 | } 198 | 199 | // On pointer move, calculate coordinates diff 200 | function onPointerMove (e) { 201 | const { x, y } = e.data.global 202 | if (pointerDownTarget) { 203 | diffX = pointerDiffStart.x + (x - pointerStart.x) 204 | diffY = pointerDiffStart.y + (y - pointerStart.y) 205 | } 206 | } 207 | 208 | // Init everything 209 | function init () { 210 | initDimensions() 211 | initUniforms() 212 | initGrid() 213 | initApp() 214 | initBackground() 215 | initContainer() 216 | initRectsAndImages() 217 | initEvents() 218 | 219 | // Animation loop 220 | // Code here will be executed on every animation frame 221 | app.ticker.add(() => { 222 | // Multiply the values by a coefficient to get a smooth animation 223 | uniforms.uPointerDown += (pointerDownTarget - uniforms.uPointerDown) * 0.075 224 | uniforms.uPointerDiff.x += (diffX - uniforms.uPointerDiff.x) * 0.2 225 | uniforms.uPointerDiff.y += (diffY - uniforms.uPointerDiff.y) * 0.2 226 | // Set position for the container 227 | container.x = uniforms.uPointerDiff.x - centerX 228 | container.y = uniforms.uPointerDiff.y - centerY 229 | }) 230 | } 231 | 232 | // Load resources, then init the app 233 | PIXI.Loader.shared.add([ 234 | 'shaders/stageFragment.glsl', 235 | 'shaders/backgroundFragment.glsl' 236 | ]).load(init) 237 | 238 | })() 239 | -------------------------------------------------------------------------------- /demos/rects-final/js/scripts.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Class to generate a random masonry layout, using a square grid as base 4 | class Grid { 5 | 6 | // The constructor receives all the following parameters: 7 | // - gridSize: The size (width and height) for smallest unit size 8 | // - gridColumns: Number of columns for the grid (width = gridColumns * gridSize) 9 | // - gridRows: Number of rows for the grid (height = gridRows * gridSize) 10 | // - gridMin: Min width and height limits for rectangles (in grid units) 11 | constructor(gridSize, gridColumns, gridRows, gridMin) { 12 | this.gridSize = gridSize 13 | this.gridColumns = gridColumns 14 | this.gridRows = gridRows 15 | this.gridMin = gridMin 16 | this.rects = [] 17 | this.currentRects = [{ x: 0, y: 0, w: this.gridColumns, h: this.gridRows }] 18 | } 19 | 20 | // Takes the first rectangle on the list, and divides it in 2 more rectangles if possible 21 | splitCurrentRect () { 22 | if (this.currentRects.length) { 23 | const currentRect = this.currentRects.shift() 24 | const cutVertical = currentRect.w > currentRect.h 25 | const cutSide = cutVertical ? currentRect.w : currentRect.h 26 | const cutSize = cutVertical ? 'w' : 'h' 27 | const cutAxis = cutVertical ? 'x' : 'y' 28 | if (cutSide > this.gridMin * 2) { 29 | const rect1Size = randomInRange(this.gridMin, cutSide - this.gridMin) 30 | const rect1 = Object.assign({}, currentRect, { [cutSize]: rect1Size }) 31 | const rect2 = Object.assign({}, currentRect, { [cutAxis]: currentRect[cutAxis] + rect1Size, [cutSize]: currentRect[cutSize] - rect1Size }) 32 | this.currentRects.push(rect1, rect2) 33 | } 34 | else { 35 | this.rects.push(currentRect) 36 | this.splitCurrentRect() 37 | } 38 | } 39 | } 40 | 41 | // Call `splitCurrentRect` until there is no more rectangles on the list 42 | // Then return the list of rectangles 43 | generateRects () { 44 | while (this.currentRects.length) { 45 | this.splitCurrentRect() 46 | } 47 | return this.rects 48 | } 49 | } 50 | 51 | // Generate a random integer in the range provided 52 | function randomInRange (min, max) { 53 | return Math.floor(Math.random() * (max - min + 1)) + min 54 | } 55 | 56 | // Get canvas view 57 | const view = document.querySelector('.view') 58 | // Loaded resources will be here 59 | const resources = PIXI.Loader.shared.resources 60 | // Target for pointer. If down, value is 1, else value is 0 61 | let pointerDownTarget = 0 62 | // Useful variables to keep track of the pointer 63 | let pointerStart = new PIXI.Point() 64 | let pointerDiffStart = new PIXI.Point() 65 | let width, height, app, background, uniforms, diffX, diffY 66 | 67 | // Variables and settings for grid 68 | const gridSize = 50 69 | const gridMin = 3 70 | const imagePadding = 20 71 | let gridColumnsCount, gridRowsCount, gridColumns, gridRows, grid 72 | let widthRest, heightRest, centerX, centerY, container, rects 73 | 74 | // Set dimensions 75 | function initDimensions () { 76 | width = window.innerWidth 77 | height = window.innerHeight 78 | diffX = 0 79 | diffY = 0 80 | } 81 | 82 | // Set initial values for uniforms 83 | function initUniforms () { 84 | uniforms = { 85 | uResolution: new PIXI.Point(width, height), 86 | uPointerDiff: new PIXI.Point(), 87 | uPointerDown: pointerDownTarget 88 | } 89 | } 90 | 91 | // Initialize the random grid layout 92 | function initGrid () { 93 | // Getting columns 94 | gridColumnsCount = Math.ceil(width / gridSize) 95 | // Getting rows 96 | gridRowsCount = Math.ceil(height / gridSize) 97 | // Make the grid 5 times bigger than viewport 98 | gridColumns = gridColumnsCount * 5 99 | gridRows = gridRowsCount * 5 100 | // Create a new Grid instance with our settings 101 | grid = new Grid(gridSize, gridColumns, gridRows, gridMin) 102 | // Calculate the center position for the grid in the viewport 103 | widthRest = Math.ceil(gridColumnsCount * gridSize - width) 104 | heightRest = Math.ceil(gridRowsCount * gridSize - height) 105 | centerX = (gridColumns * gridSize / 2) - (gridColumnsCount * gridSize / 2) 106 | centerY = (gridRows * gridSize / 2) - (gridRowsCount * gridSize / 2) 107 | // Generate the list of rects 108 | rects = grid.generateRects() 109 | } 110 | 111 | // Init the PixiJS Application 112 | function initApp () { 113 | // Create a PixiJS Application, using the view (canvas) provided 114 | app = new PIXI.Application({ view }) 115 | // Resizes renderer view in CSS pixels to allow for resolutions other than 1 116 | app.renderer.autoDensity = true 117 | // Resize the view to match viewport dimensions 118 | app.renderer.resize(width, height) 119 | 120 | // Set the distortion filter for the entire stage 121 | const stageFragmentShader = resources['shaders/stageFragment.glsl'].data 122 | const stageFilter = new PIXI.Filter(undefined, stageFragmentShader, uniforms) 123 | app.stage.filters = [stageFilter] 124 | } 125 | 126 | // Init the gridded background 127 | function initBackground () { 128 | // Create a new empty Sprite and define its size 129 | background = new PIXI.Sprite() 130 | background.width = width 131 | background.height = height 132 | // Get the code for the fragment shader from the loaded resources 133 | const backgroundFragmentShader = resources['shaders/backgroundFragment.glsl'].data 134 | // Create a new Filter using the fragment shader 135 | // We don't need a custom vertex shader, so we set it as `undefined` 136 | const backgroundFilter = new PIXI.Filter(undefined, backgroundFragmentShader, uniforms) 137 | // Assign the filter to the background Sprite 138 | background.filters = [backgroundFilter] 139 | // Add the background to the stage 140 | app.stage.addChild(background) 141 | } 142 | 143 | // Initialize a Container element for solid rectangles and images 144 | function initContainer () { 145 | container = new PIXI.Container() 146 | app.stage.addChild(container) 147 | } 148 | 149 | // Add solid rectangles and images 150 | // So far, we will only add rectangles 151 | function initRectsAndImages () { 152 | // Create a new Graphics element to draw solid rectangles 153 | const graphics = new PIXI.Graphics() 154 | // Select the color for rectangles 155 | graphics.beginFill(0xAA22CC) 156 | // Loop over each rect in the list 157 | rects.forEach(rect => { 158 | // Draw the rectangle 159 | graphics.drawRect( 160 | rect.x * gridSize, 161 | rect.y * gridSize, 162 | rect.w * gridSize - imagePadding, 163 | rect.h * gridSize - imagePadding 164 | ) 165 | }) 166 | // Ends the fill action 167 | graphics.endFill() 168 | // Add the graphics (with all drawn rects) to the container 169 | container.addChild(graphics) 170 | } 171 | 172 | // Start listening events 173 | function initEvents () { 174 | // Make stage interactive, so it can listen to events 175 | app.stage.interactive = true 176 | 177 | // Pointer & touch events are normalized into 178 | // the `pointer*` events for handling different events 179 | app.stage 180 | .on('pointerdown', onPointerDown) 181 | .on('pointerup', onPointerUp) 182 | .on('pointerupoutside', onPointerUp) 183 | .on('pointermove', onPointerMove) 184 | } 185 | 186 | // On pointer down, save coordinates and set pointerDownTarget 187 | function onPointerDown (e) { 188 | const { x, y } = e.data.global 189 | pointerDownTarget = 1 190 | pointerStart.set(x, y) 191 | pointerDiffStart = uniforms.uPointerDiff.clone() 192 | } 193 | 194 | // On pointer up, set pointerDownTarget 195 | function onPointerUp () { 196 | pointerDownTarget = 0 197 | } 198 | 199 | // On pointer move, calculate coordinates diff 200 | function onPointerMove (e) { 201 | const { x, y } = e.data.global 202 | if (pointerDownTarget) { 203 | diffX = pointerDiffStart.x + (x - pointerStart.x) 204 | diffY = pointerDiffStart.y + (y - pointerStart.y) 205 | diffX = diffX > 0 ? Math.min(diffX, centerX + imagePadding) : Math.max(diffX, -(centerX + widthRest)) 206 | diffY = diffY > 0 ? Math.min(diffY, centerY + imagePadding) : Math.max(diffY, -(centerY + heightRest)) 207 | } 208 | } 209 | 210 | // Init everything 211 | function init () { 212 | initDimensions() 213 | initUniforms() 214 | initGrid() 215 | initApp() 216 | initBackground() 217 | initContainer() 218 | initRectsAndImages() 219 | initEvents() 220 | 221 | // Animation loop 222 | // Code here will be executed on every animation frame 223 | app.ticker.add(() => { 224 | // Multiply the values by a coefficient to get a smooth animation 225 | uniforms.uPointerDown += (pointerDownTarget - uniforms.uPointerDown) * 0.075 226 | uniforms.uPointerDiff.x += (diffX - uniforms.uPointerDiff.x) * 0.2 227 | uniforms.uPointerDiff.y += (diffY - uniforms.uPointerDiff.y) * 0.2 228 | // Set position for the container 229 | container.x = uniforms.uPointerDiff.x - centerX 230 | container.y = uniforms.uPointerDiff.y - centerY 231 | }) 232 | } 233 | 234 | // Load resources, then init the app 235 | PIXI.Loader.shared.add([ 236 | 'shaders/stageFragment.glsl', 237 | 'shaders/backgroundFragment.glsl' 238 | ]).load(init) 239 | 240 | })() 241 | -------------------------------------------------------------------------------- /demos/images/js/scripts.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Class to generate a random masonry layout, using a square grid as base 4 | class Grid { 5 | 6 | // The constructor receives all the following parameters: 7 | // - gridSize: The size (width and height) for smallest unit size 8 | // - gridColumns: Number of columns for the grid (width = gridColumns * gridSize) 9 | // - gridRows: Number of rows for the grid (height = gridRows * gridSize) 10 | // - gridMin: Min width and height limits for rectangles (in grid units) 11 | constructor(gridSize, gridColumns, gridRows, gridMin) { 12 | this.gridSize = gridSize 13 | this.gridColumns = gridColumns 14 | this.gridRows = gridRows 15 | this.gridMin = gridMin 16 | this.rects = [] 17 | this.currentRects = [{ x: 0, y: 0, w: this.gridColumns, h: this.gridRows }] 18 | } 19 | 20 | // Takes the first rectangle on the list, and divides it in 2 more rectangles if possible 21 | splitCurrentRect () { 22 | if (this.currentRects.length) { 23 | const currentRect = this.currentRects.shift() 24 | const cutVertical = currentRect.w > currentRect.h 25 | const cutSide = cutVertical ? currentRect.w : currentRect.h 26 | const cutSize = cutVertical ? 'w' : 'h' 27 | const cutAxis = cutVertical ? 'x' : 'y' 28 | if (cutSide > this.gridMin * 2) { 29 | const rect1Size = randomInRange(this.gridMin, cutSide - this.gridMin) 30 | const rect1 = Object.assign({}, currentRect, { [cutSize]: rect1Size }) 31 | const rect2 = Object.assign({}, currentRect, { [cutAxis]: currentRect[cutAxis] + rect1Size, [cutSize]: currentRect[cutSize] - rect1Size }) 32 | this.currentRects.push(rect1, rect2) 33 | } 34 | else { 35 | this.rects.push(currentRect) 36 | this.splitCurrentRect() 37 | } 38 | } 39 | } 40 | 41 | // Call `splitCurrentRect` until there is no more rectangles on the list 42 | // Then return the list of rectangles 43 | generateRects () { 44 | while (this.currentRects.length) { 45 | this.splitCurrentRect() 46 | } 47 | return this.rects 48 | } 49 | } 50 | 51 | // Generate a random integer in the range provided 52 | function randomInRange (min, max) { 53 | return Math.floor(Math.random() * (max - min + 1)) + min 54 | } 55 | 56 | // Get canvas view 57 | const view = document.querySelector('.view') 58 | // Loaded resources will be here 59 | const resources = PIXI.Loader.shared.resources 60 | // Target for pointer. If down, value is 1, else value is 0 61 | let pointerDownTarget = 0 62 | // Useful variables to keep track of the pointer 63 | let pointerStart = new PIXI.Point() 64 | let pointerDiffStart = new PIXI.Point() 65 | let width, height, app, background, uniforms, diffX, diffY 66 | 67 | // Variables and settings for grid 68 | const gridSize = 50 69 | const gridMin = 3 70 | const imagePadding = 20 71 | let gridColumnsCount, gridRowsCount, gridColumns, gridRows, grid 72 | let widthRest, heightRest, centerX, centerY, container, rects 73 | let images, imagesUrls 74 | 75 | // Set dimensions 76 | function initDimensions () { 77 | width = window.innerWidth 78 | height = window.innerHeight 79 | diffX = 0 80 | diffY = 0 81 | } 82 | 83 | // Set initial values for uniforms 84 | function initUniforms () { 85 | uniforms = { 86 | uResolution: new PIXI.Point(width, height), 87 | uPointerDiff: new PIXI.Point(), 88 | uPointerDown: pointerDownTarget 89 | } 90 | } 91 | 92 | // Initialize the random grid layout 93 | function initGrid () { 94 | // Getting columns 95 | gridColumnsCount = Math.ceil(width / gridSize) 96 | // Getting rows 97 | gridRowsCount = Math.ceil(height / gridSize) 98 | // Make the grid 5 times bigger than viewport 99 | gridColumns = gridColumnsCount * 5 100 | gridRows = gridRowsCount * 5 101 | // Create a new Grid instance with our settings 102 | grid = new Grid(gridSize, gridColumns, gridRows, gridMin) 103 | // Calculate the center position for the grid in the viewport 104 | widthRest = Math.ceil(gridColumnsCount * gridSize - width) 105 | heightRest = Math.ceil(gridRowsCount * gridSize - height) 106 | centerX = (gridColumns * gridSize / 2) - (gridColumnsCount * gridSize / 2) 107 | centerY = (gridRows * gridSize / 2) - (gridRowsCount * gridSize / 2) 108 | // Generate the list of rects 109 | rects = grid.generateRects() 110 | // For the list of images 111 | images = [] 112 | // For storing the image URL and avoid duplicates 113 | imagesUrls = {} 114 | } 115 | 116 | // Init the PixiJS Application 117 | function initApp () { 118 | // Create a PixiJS Application, using the view (canvas) provided 119 | app = new PIXI.Application({ view }) 120 | // Resizes renderer view in CSS pixels to allow for resolutions other than 1 121 | app.renderer.autoDensity = true 122 | // Resize the view to match viewport dimensions 123 | app.renderer.resize(width, height) 124 | 125 | // Set the distortion filter for the entire stage 126 | const stageFragmentShader = resources['shaders/stageFragment.glsl'].data 127 | const stageFilter = new PIXI.Filter(undefined, stageFragmentShader, uniforms) 128 | app.stage.filters = [stageFilter] 129 | } 130 | 131 | // Init the gridded background 132 | function initBackground () { 133 | // Create a new empty Sprite and define its size 134 | background = new PIXI.Sprite() 135 | background.width = width 136 | background.height = height 137 | // Get the code for the fragment shader from the loaded resources 138 | const backgroundFragmentShader = resources['shaders/backgroundFragment.glsl'].data 139 | // Create a new Filter using the fragment shader 140 | // We don't need a custom vertex shader, so we set it as `undefined` 141 | const backgroundFilter = new PIXI.Filter(undefined, backgroundFragmentShader, uniforms) 142 | // Assign the filter to the background Sprite 143 | background.filters = [backgroundFilter] 144 | // Add the background to the stage 145 | app.stage.addChild(background) 146 | } 147 | 148 | // Initialize a Container element for solid rectangles and images 149 | function initContainer () { 150 | container = new PIXI.Container() 151 | app.stage.addChild(container) 152 | } 153 | 154 | // Load texture for an image, giving its index 155 | function loadTextureForImage (index) { 156 | // Get image Sprite 157 | const image = images[index] 158 | // Set the url to get a random image from Unsplash Source, given image dimensions 159 | const url = `https://source.unsplash.com/random/${image.width}x${image.height}` 160 | // Get the corresponding rect, to store more data needed (it is a normal Object) 161 | const rect = rects[index] 162 | // Create a new AbortController, to abort fetch if needed 163 | const { signal } = rect.controller = new AbortController() 164 | // Fetch the image 165 | fetch(url, { signal }).then(response => { 166 | // Get image URL, and if it was downloaded before, load another image 167 | // Otherwise, save image URL and set the texture 168 | const id = response.url.split('?')[0] 169 | if (imagesUrls[id]) { 170 | loadTextureForImage(index) 171 | } else { 172 | imagesUrls[id] = true 173 | image.texture = PIXI.Texture.from(response.url) 174 | rect.loaded = true 175 | } 176 | }).catch(() => { 177 | // Catch errors silently, for not showing the following error message if it is aborted: 178 | // AbortError: The operation was aborted. 179 | }) 180 | } 181 | 182 | // Add solid rectangles and images 183 | function initRectsAndImages () { 184 | // Create a new Graphics element to draw solid rectangles 185 | const graphics = new PIXI.Graphics() 186 | // Select the color for rectangles 187 | graphics.beginFill(0x000000) 188 | // Loop over each rect in the list 189 | rects.forEach(rect => { 190 | // Create a new Sprite element for each image 191 | const image = new PIXI.Sprite() 192 | // Set image's position and size 193 | image.x = rect.x * gridSize 194 | image.y = rect.y * gridSize 195 | image.width = rect.w * gridSize - imagePadding 196 | image.height = rect.h * gridSize - imagePadding 197 | // Set it's alpha to 0, so it is not visible initially 198 | image.alpha = 0 199 | // Add image to the list 200 | images.push(image) 201 | // Draw the rectangle 202 | graphics.drawRect(image.x, image.y, image.width, image.height) 203 | }) 204 | // Ends the fill action 205 | graphics.endFill() 206 | // Add the graphics (with all drawn rects) to the container 207 | container.addChild(graphics) 208 | // Add all image's Sprites to the container 209 | images.forEach(image => { 210 | container.addChild(image) 211 | }) 212 | } 213 | 214 | // Check if rects intersects with the viewport 215 | // and loads corresponding image 216 | function checkRectsAndImages () { 217 | // Loop over rects 218 | rects.forEach((rect, index) => { 219 | // Get corresponding image 220 | const image = images[index] 221 | // Check if the rect intersects with the viewport 222 | if (rectIntersectsWithViewport(rect)) { 223 | // If rect just has been discovered 224 | // start loading image 225 | if (!rect.discovered) { 226 | rect.discovered = true 227 | loadTextureForImage(index) 228 | } 229 | // If image is loaded, increase alpha if possible 230 | if (rect.loaded && image.alpha < 1) { 231 | image.alpha += 0.01 232 | } 233 | } else { // The rect is not intersecting 234 | // If the rect was discovered before, but the 235 | // image is not loaded yet, abort the fetch 236 | if (rect.discovered && !rect.loaded) { 237 | rect.discovered = false 238 | rect.controller.abort() 239 | } 240 | // Decrease alpha if possible 241 | if (image.alpha > 0) { 242 | image.alpha -= 0.01 243 | } 244 | } 245 | }) 246 | } 247 | 248 | // Check if a rect intersects the viewport 249 | function rectIntersectsWithViewport (rect) { 250 | return ( 251 | rect.x * gridSize + container.x <= width && 252 | 0 <= (rect.x + rect.w) * gridSize + container.x && 253 | rect.y * gridSize + container.y <= height && 254 | 0 <= (rect.y + rect.h) * gridSize + container.y 255 | ) 256 | } 257 | 258 | // Start listening events 259 | function initEvents () { 260 | // Make stage interactive, so it can listen to events 261 | app.stage.interactive = true 262 | 263 | // Pointer & touch events are normalized into 264 | // the `pointer*` events for handling different events 265 | app.stage 266 | .on('pointerdown', onPointerDown) 267 | .on('pointerup', onPointerUp) 268 | .on('pointerupoutside', onPointerUp) 269 | .on('pointermove', onPointerMove) 270 | } 271 | 272 | // On pointer down, save coordinates and set pointerDownTarget 273 | function onPointerDown (e) { 274 | const { x, y } = e.data.global 275 | pointerDownTarget = 1 276 | pointerStart.set(x, y) 277 | pointerDiffStart = uniforms.uPointerDiff.clone() 278 | } 279 | 280 | // On pointer up, set pointerDownTarget 281 | function onPointerUp () { 282 | pointerDownTarget = 0 283 | } 284 | 285 | // On pointer move, calculate coordinates diff 286 | function onPointerMove (e) { 287 | const { x, y } = e.data.global 288 | if (pointerDownTarget) { 289 | diffX = pointerDiffStart.x + (x - pointerStart.x) 290 | diffY = pointerDiffStart.y + (y - pointerStart.y) 291 | diffX = diffX > 0 ? Math.min(diffX, centerX + imagePadding) : Math.max(diffX, -(centerX + widthRest)) 292 | diffY = diffY > 0 ? Math.min(diffY, centerY + imagePadding) : Math.max(diffY, -(centerY + heightRest)) 293 | } 294 | } 295 | 296 | // Init everything 297 | function init () { 298 | initDimensions() 299 | initUniforms() 300 | initGrid() 301 | initApp() 302 | initBackground() 303 | initContainer() 304 | initRectsAndImages() 305 | initEvents() 306 | 307 | // Animation loop 308 | // Code here will be executed on every animation frame 309 | app.ticker.add(() => { 310 | // Multiply the values by a coefficient to get a smooth animation 311 | uniforms.uPointerDown += (pointerDownTarget - uniforms.uPointerDown) * 0.075 312 | uniforms.uPointerDiff.x += (diffX - uniforms.uPointerDiff.x) * 0.2 313 | uniforms.uPointerDiff.y += (diffY - uniforms.uPointerDiff.y) * 0.2 314 | // Set position for the container 315 | container.x = uniforms.uPointerDiff.x - centerX 316 | container.y = uniforms.uPointerDiff.y - centerY 317 | // Check rects and load/cancel images as needded 318 | checkRectsAndImages() 319 | }) 320 | } 321 | 322 | // Load resources, then init the app 323 | PIXI.Loader.shared.add([ 324 | 'shaders/stageFragment.glsl', 325 | 'shaders/backgroundFragment.glsl' 326 | ]).load(init) 327 | 328 | })() 329 | -------------------------------------------------------------------------------- /demos/final/js/scripts.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Class to generate a random masonry layout, using a square grid as base 4 | class Grid { 5 | 6 | // The constructor receives all the following parameters: 7 | // - gridSize: The size (width and height) for smallest unit size 8 | // - gridColumns: Number of columns for the grid (width = gridColumns * gridSize) 9 | // - gridRows: Number of rows for the grid (height = gridRows * gridSize) 10 | // - gridMin: Min width and height limits for rectangles (in grid units) 11 | constructor(gridSize, gridColumns, gridRows, gridMin) { 12 | this.gridSize = gridSize 13 | this.gridColumns = gridColumns 14 | this.gridRows = gridRows 15 | this.gridMin = gridMin 16 | this.rects = [] 17 | this.currentRects = [{ x: 0, y: 0, w: this.gridColumns, h: this.gridRows }] 18 | } 19 | 20 | // Takes the first rectangle on the list, and divides it in 2 more rectangles if possible 21 | splitCurrentRect () { 22 | if (this.currentRects.length) { 23 | const currentRect = this.currentRects.shift() 24 | const cutVertical = currentRect.w > currentRect.h 25 | const cutSide = cutVertical ? currentRect.w : currentRect.h 26 | const cutSize = cutVertical ? 'w' : 'h' 27 | const cutAxis = cutVertical ? 'x' : 'y' 28 | if (cutSide > this.gridMin * 2) { 29 | const rect1Size = randomInRange(this.gridMin, cutSide - this.gridMin) 30 | const rect1 = Object.assign({}, currentRect, { [cutSize]: rect1Size }) 31 | const rect2 = Object.assign({}, currentRect, { [cutAxis]: currentRect[cutAxis] + rect1Size, [cutSize]: currentRect[cutSize] - rect1Size }) 32 | this.currentRects.push(rect1, rect2) 33 | } 34 | else { 35 | this.rects.push(currentRect) 36 | this.splitCurrentRect() 37 | } 38 | } 39 | } 40 | 41 | // Call `splitCurrentRect` until there is no more rectangles on the list 42 | // Then return the list of rectangles 43 | generateRects () { 44 | while (this.currentRects.length) { 45 | this.splitCurrentRect() 46 | } 47 | return this.rects 48 | } 49 | } 50 | 51 | // Generate a random integer in the range provided 52 | function randomInRange (min, max) { 53 | return Math.floor(Math.random() * (max - min + 1)) + min 54 | } 55 | 56 | // Get canvas view 57 | const view = document.querySelector('.view') 58 | // Loaded resources will be here 59 | const resources = PIXI.Loader.shared.resources 60 | // Target for pointer. If down, value is 1, else value is 0 61 | let pointerDownTarget = 0 62 | // Useful variables to keep track of the pointer 63 | let pointerStart = new PIXI.Point() 64 | let pointerDiffStart = new PIXI.Point() 65 | let width, height, app, background, uniforms, diffX, diffY 66 | 67 | // Variables and settings for grid 68 | const gridSize = 50 69 | const gridMin = 3 70 | const imagePadding = 20 71 | let gridColumnsCount, gridRowsCount, gridColumns, gridRows, grid 72 | let widthRest, heightRest, centerX, centerY, container, rects 73 | let images, imagesUrls 74 | 75 | // Set dimensions 76 | function initDimensions () { 77 | width = window.innerWidth 78 | height = window.innerHeight 79 | diffX = 0 80 | diffY = 0 81 | } 82 | 83 | // Set initial values for uniforms 84 | function initUniforms () { 85 | uniforms = { 86 | uResolution: new PIXI.Point(width, height), 87 | uPointerDiff: new PIXI.Point(), 88 | uPointerDown: pointerDownTarget 89 | } 90 | } 91 | 92 | // Initialize the random grid layout 93 | function initGrid () { 94 | // Getting columns 95 | gridColumnsCount = Math.ceil(width / gridSize) 96 | // Getting rows 97 | gridRowsCount = Math.ceil(height / gridSize) 98 | // Make the grid 5 times bigger than viewport 99 | gridColumns = gridColumnsCount * 5 100 | gridRows = gridRowsCount * 5 101 | // Create a new Grid instance with our settings 102 | grid = new Grid(gridSize, gridColumns, gridRows, gridMin) 103 | // Calculate the center position for the grid in the viewport 104 | widthRest = Math.ceil(gridColumnsCount * gridSize - width) 105 | heightRest = Math.ceil(gridRowsCount * gridSize - height) 106 | centerX = (gridColumns * gridSize / 2) - (gridColumnsCount * gridSize / 2) 107 | centerY = (gridRows * gridSize / 2) - (gridRowsCount * gridSize / 2) 108 | // Generate the list of rects 109 | rects = grid.generateRects() 110 | // For the list of images 111 | images = [] 112 | // For storing the image URL and avoid duplicates 113 | imagesUrls = {} 114 | } 115 | 116 | // Init the PixiJS Application 117 | function initApp () { 118 | // Create a PixiJS Application, using the view (canvas) provided 119 | app = new PIXI.Application({ view }) 120 | // Resizes renderer view in CSS pixels to allow for resolutions other than 1 121 | app.renderer.autoDensity = true 122 | // Resize the view to match viewport dimensions 123 | app.renderer.resize(width, height) 124 | 125 | // Set the distortion filter for the entire stage 126 | const stageFragmentShader = resources['shaders/stageFragment.glsl'].data 127 | const stageFilter = new PIXI.Filter(undefined, stageFragmentShader, uniforms) 128 | app.stage.filters = [stageFilter] 129 | } 130 | 131 | // Init the gridded background 132 | function initBackground () { 133 | // Create a new empty Sprite and define its size 134 | background = new PIXI.Sprite() 135 | background.width = width 136 | background.height = height 137 | // Get the code for the fragment shader from the loaded resources 138 | const backgroundFragmentShader = resources['shaders/backgroundFragment.glsl'].data 139 | // Create a new Filter using the fragment shader 140 | // We don't need a custom vertex shader, so we set it as `undefined` 141 | const backgroundFilter = new PIXI.Filter(undefined, backgroundFragmentShader, uniforms) 142 | // Assign the filter to the background Sprite 143 | background.filters = [backgroundFilter] 144 | // Add the background to the stage 145 | app.stage.addChild(background) 146 | } 147 | 148 | // Initialize a Container element for solid rectangles and images 149 | function initContainer () { 150 | container = new PIXI.Container() 151 | app.stage.addChild(container) 152 | } 153 | 154 | // Load texture for an image, giving its index 155 | function loadTextureForImage (index) { 156 | // Get image Sprite 157 | const image = images[index] 158 | // Set the url to get a random image from Unsplash Source, given image dimensions 159 | const url = `https://source.unsplash.com/random/${image.width}x${image.height}` 160 | // Get the corresponding rect, to store more data needed (it is a normal Object) 161 | const rect = rects[index] 162 | // Create a new AbortController, to abort fetch if needed 163 | const { signal } = rect.controller = new AbortController() 164 | // Fetch the image 165 | fetch(url, { signal }).then(response => { 166 | // Get image URL, and if it was downloaded before, load another image 167 | // Otherwise, save image URL and set the texture 168 | const id = response.url.split('?')[0] 169 | if (imagesUrls[id]) { 170 | loadTextureForImage(index) 171 | } else { 172 | imagesUrls[id] = true 173 | image.texture = PIXI.Texture.from(response.url) 174 | rect.loaded = true 175 | } 176 | }).catch(() => { 177 | // Catch errors silently, for not showing the following error message if it is aborted: 178 | // AbortError: The operation was aborted. 179 | }) 180 | } 181 | 182 | // Add solid rectangles and images 183 | function initRectsAndImages () { 184 | // Create a new Graphics element to draw solid rectangles 185 | const graphics = new PIXI.Graphics() 186 | // Select the color for rectangles 187 | graphics.beginFill(0x000000) 188 | // Loop over each rect in the list 189 | rects.forEach(rect => { 190 | // Create a new Sprite element for each image 191 | const image = new PIXI.Sprite() 192 | // Set image's position and size 193 | image.x = rect.x * gridSize 194 | image.y = rect.y * gridSize 195 | image.width = rect.w * gridSize - imagePadding 196 | image.height = rect.h * gridSize - imagePadding 197 | // Set it's alpha to 0, so it is not visible initially 198 | image.alpha = 0 199 | // Add image to the list 200 | images.push(image) 201 | // Draw the rectangle 202 | graphics.drawRect(image.x, image.y, image.width, image.height) 203 | }) 204 | // Ends the fill action 205 | graphics.endFill() 206 | // Add the graphics (with all drawn rects) to the container 207 | container.addChild(graphics) 208 | // Add all image's Sprites to the container 209 | images.forEach(image => { 210 | container.addChild(image) 211 | }) 212 | } 213 | 214 | // Check if rects intersects with the viewport 215 | // and loads corresponding image 216 | function checkRectsAndImages () { 217 | // Loop over rects 218 | rects.forEach((rect, index) => { 219 | // Get corresponding image 220 | const image = images[index] 221 | // Check if the rect intersects with the viewport 222 | if (rectIntersectsWithViewport(rect)) { 223 | // If rect just has been discovered 224 | // start loading image 225 | if (!rect.discovered) { 226 | rect.discovered = true 227 | loadTextureForImage(index) 228 | } 229 | // If image is loaded, increase alpha if possible 230 | if (rect.loaded && image.alpha < 1) { 231 | image.alpha += 0.01 232 | } 233 | } else { // The rect is not intersecting 234 | // If the rect was discovered before, but the 235 | // image is not loaded yet, abort the fetch 236 | if (rect.discovered && !rect.loaded) { 237 | rect.discovered = false 238 | rect.controller.abort() 239 | } 240 | // Decrease alpha if possible 241 | if (image.alpha > 0) { 242 | image.alpha -= 0.01 243 | } 244 | } 245 | }) 246 | } 247 | 248 | // Check if a rect intersects the viewport 249 | function rectIntersectsWithViewport (rect) { 250 | return ( 251 | rect.x * gridSize + container.x <= width && 252 | 0 <= (rect.x + rect.w) * gridSize + container.x && 253 | rect.y * gridSize + container.y <= height && 254 | 0 <= (rect.y + rect.h) * gridSize + container.y 255 | ) 256 | } 257 | 258 | // Start listening events 259 | function initEvents () { 260 | // Make stage interactive, so it can listen to events 261 | app.stage.interactive = true 262 | 263 | // Pointer & touch events are normalized into 264 | // the `pointer*` events for handling different events 265 | app.stage 266 | .on('pointerdown', onPointerDown) 267 | .on('pointerup', onPointerUp) 268 | .on('pointerupoutside', onPointerUp) 269 | .on('pointermove', onPointerMove) 270 | } 271 | 272 | // On pointer down, save coordinates and set pointerDownTarget 273 | function onPointerDown (e) { 274 | const { x, y } = e.data.global 275 | pointerDownTarget = 1 276 | pointerStart.set(x, y) 277 | pointerDiffStart = uniforms.uPointerDiff.clone() 278 | } 279 | 280 | // On pointer up, set pointerDownTarget 281 | function onPointerUp () { 282 | pointerDownTarget = 0 283 | } 284 | 285 | // On pointer move, calculate coordinates diff 286 | function onPointerMove (e) { 287 | const { x, y } = e.data.global 288 | if (pointerDownTarget) { 289 | diffX = pointerDiffStart.x + (x - pointerStart.x) 290 | diffY = pointerDiffStart.y + (y - pointerStart.y) 291 | diffX = diffX > 0 ? Math.min(diffX, centerX + imagePadding) : Math.max(diffX, -(centerX + widthRest)) 292 | diffY = diffY > 0 ? Math.min(diffY, centerY + imagePadding) : Math.max(diffY, -(centerY + heightRest)) 293 | } 294 | } 295 | 296 | // Init everything 297 | function init () { 298 | initDimensions() 299 | initUniforms() 300 | initGrid() 301 | initApp() 302 | initBackground() 303 | initContainer() 304 | initRectsAndImages() 305 | initEvents() 306 | 307 | // Animation loop 308 | // Code here will be executed on every animation frame 309 | app.ticker.add(() => { 310 | // Multiply the values by a coefficient to get a smooth animation 311 | uniforms.uPointerDown += (pointerDownTarget - uniforms.uPointerDown) * 0.075 312 | uniforms.uPointerDiff.x += (diffX - uniforms.uPointerDiff.x) * 0.2 313 | uniforms.uPointerDiff.y += (diffY - uniforms.uPointerDiff.y) * 0.2 314 | // Set position for the container 315 | container.x = uniforms.uPointerDiff.x - centerX 316 | container.y = uniforms.uPointerDiff.y - centerY 317 | // Check rects and load/cancel images as needded 318 | checkRectsAndImages() 319 | }) 320 | } 321 | 322 | // Clean the current Application 323 | function clean () { 324 | // Stop the current animation 325 | app.ticker.stop() 326 | 327 | // Remove event listeners 328 | app.stage 329 | .off('pointerdown', onPointerDown) 330 | .off('pointerup', onPointerUp) 331 | .off('pointerupoutside', onPointerUp) 332 | .off('pointermove', onPointerMove) 333 | 334 | // Abort all fetch calls in progress 335 | rects.forEach(rect => { 336 | if (rect.discovered && !rect.loaded) { 337 | rect.controller.abort() 338 | } 339 | }) 340 | } 341 | 342 | // On resize, reinit the app (clean and init) 343 | // But first debounce the calls, so we don't call init too often 344 | let resizeTimer 345 | function onResize () { 346 | if (resizeTimer) clearTimeout(resizeTimer) 347 | resizeTimer = setTimeout(() => { 348 | clean() 349 | init() 350 | }, 200) 351 | } 352 | // Listen to resize event 353 | window.addEventListener('resize', onResize) 354 | 355 | // Load resources, then init the app 356 | PIXI.Loader.shared.add([ 357 | 'shaders/stageFragment.glsl', 358 | 'shaders/backgroundFragment.glsl' 359 | ]).load(init) 360 | 361 | })() 362 | -------------------------------------------------------------------------------- /js/scripts.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Class to generate a random masonry layout, using a square grid as base 4 | class Grid { 5 | 6 | // The constructor receives all the following parameters: 7 | // - gridSize: The size (width and height) for smallest unit size 8 | // - gridColumns: Number of columns for the grid (width = gridColumns * gridSize) 9 | // - gridRows: Number of rows for the grid (height = gridRows * gridSize) 10 | // - gridMin: Min width and height limits for rectangles (in grid units) 11 | constructor(gridSize, gridColumns, gridRows, gridMin) { 12 | this.gridSize = gridSize 13 | this.gridColumns = gridColumns 14 | this.gridRows = gridRows 15 | this.gridMin = gridMin 16 | this.rects = [] 17 | this.currentRects = [{ x: 0, y: 0, w: this.gridColumns, h: this.gridRows }] 18 | } 19 | 20 | // Takes the first rectangle on the list, and divides it in 2 more rectangles if possible 21 | splitCurrentRect () { 22 | if (this.currentRects.length) { 23 | const currentRect = this.currentRects.shift() 24 | const cutVertical = currentRect.w > currentRect.h 25 | const cutSide = cutVertical ? currentRect.w : currentRect.h 26 | const cutSize = cutVertical ? 'w' : 'h' 27 | const cutAxis = cutVertical ? 'x' : 'y' 28 | if (cutSide > this.gridMin * 2) { 29 | const rect1Size = randomInRange(this.gridMin, cutSide - this.gridMin) 30 | const rect1 = Object.assign({}, currentRect, { [cutSize]: rect1Size }) 31 | const rect2 = Object.assign({}, currentRect, { [cutAxis]: currentRect[cutAxis] + rect1Size, [cutSize]: currentRect[cutSize] - rect1Size }) 32 | this.currentRects.push(rect1, rect2) 33 | } 34 | else { 35 | this.rects.push(currentRect) 36 | this.splitCurrentRect() 37 | } 38 | } 39 | } 40 | 41 | // Call `splitCurrentRect` until there is no more rectangles on the list 42 | // Then return the list of rectangles 43 | generateRects () { 44 | while (this.currentRects.length) { 45 | this.splitCurrentRect() 46 | } 47 | return this.rects 48 | } 49 | } 50 | 51 | // Generate a random integer in the range provided 52 | function randomInRange (min, max) { 53 | return Math.floor(Math.random() * (max - min + 1)) + min 54 | } 55 | 56 | // Get canvas view 57 | const view = document.querySelector('.view') 58 | // Loaded resources will be here 59 | const resources = PIXI.Loader.shared.resources 60 | // Target for pointer. If down, value is 1, else value is 0 61 | let pointerDownTarget = 0 62 | // Useful variables to keep track of the pointer 63 | let pointerStart = new PIXI.Point() 64 | let pointerDiffStart = new PIXI.Point() 65 | let width, height 66 | let container, background 67 | let uniforms 68 | let app 69 | let diffX, diffY 70 | 71 | // Variables and settings for grid 72 | const gridSize = 50 73 | const gridMin = 3 74 | const imagePadding = 20 75 | let gridColumnsCount, gridRowsCount, gridColumns, gridRows, grid 76 | let widthRest, heightRest, centerX, centerY 77 | let rects, images, imagesUrls 78 | 79 | // Set dimensions 80 | function initDimensions () { 81 | width = window.innerWidth 82 | height = window.innerHeight 83 | diffX = 0 84 | diffY = 0 85 | } 86 | 87 | // Set initial values for uniforms 88 | function initUniforms () { 89 | uniforms = { 90 | uResolution: new PIXI.Point(width, height), 91 | uPointerDiff: new PIXI.Point(), 92 | uPointerDown: pointerDownTarget 93 | } 94 | } 95 | 96 | // Initialize the random grid layout 97 | function initGrid () { 98 | // Getting columns 99 | gridColumnsCount = Math.ceil(width / gridSize) 100 | // Getting rows 101 | gridRowsCount = Math.ceil(height / gridSize) 102 | // Make the grid 5 times bigger than viewport 103 | gridColumns = gridColumnsCount * 5 104 | gridRows = gridRowsCount * 5 105 | // Create a new Grid instance with our settings 106 | grid = new Grid(gridSize, gridColumns, gridRows, gridMin) 107 | // Calculate the center position for the grid in the viewport 108 | widthRest = Math.ceil(gridColumnsCount * gridSize - width) 109 | heightRest = Math.ceil(gridRowsCount * gridSize - height) 110 | centerX = (gridColumns * gridSize / 2) - (gridColumnsCount * gridSize / 2) 111 | centerY = (gridRows * gridSize / 2) - (gridRowsCount * gridSize / 2) 112 | // Generate the list of rects 113 | rects = grid.generateRects() 114 | // For the list of images 115 | images = [] 116 | // For storing the image URL and avoid duplicates 117 | imagesUrls = {} 118 | } 119 | 120 | // Init the PixiJS Application 121 | function initApp () { 122 | // Create a PixiJS Application, using the view (canvas) provided 123 | app = new PIXI.Application({ view }) 124 | // Resizes renderer view in CSS pixels to allow for resolutions other than 1 125 | app.renderer.autoDensity = true 126 | // Resize the view to match viewport size 127 | app.renderer.resize(width, height) 128 | 129 | // Set the distortion filter for the entire stage 130 | const stageFragmentShader = resources['shaders/stageFragment.glsl'].data 131 | const stageFilter = new PIXI.Filter(undefined, stageFragmentShader, uniforms) 132 | app.stage.filters = [stageFilter] 133 | } 134 | 135 | // Init the gridded background 136 | function initBackground () { 137 | // Create a new empty Sprite and define its size 138 | background = new PIXI.Sprite() 139 | background.width = width 140 | background.height = height 141 | // Get the code for the fragment shader from the loaded resources 142 | const backgroundFragmentShader = resources['shaders/backgroundFragment.glsl'].data 143 | // Create a new Filter using the fragment shader and the uniforms 144 | // We don't need a custom vertex shader, so we set it as `undefined` 145 | const backgroundFilter = new PIXI.Filter(undefined, backgroundFragmentShader, uniforms) 146 | // Assign the filter to the background Sprite 147 | background.filters = [backgroundFilter] 148 | // Add the background to the stage 149 | app.stage.addChild(background) 150 | } 151 | 152 | // Initialize a Container element for solid rectangles and images 153 | function initContainer () { 154 | container = new PIXI.Container() 155 | app.stage.addChild(container) 156 | } 157 | 158 | // Load texture for an image, giving its index 159 | function loadTextureForImage (index) { 160 | // Get image Sprite 161 | const image = images[index] 162 | // Set the url to get a random image from Unsplash Source, given image dimensions 163 | const url = `https://source.unsplash.com/random/${image.width}x${image.height}` 164 | // Get the corresponding rect, to store more data needed (it is a normal Object) 165 | const rect = rects[index] 166 | // Create a new AbortController, to abort fetch if needed 167 | const { signal } = rect.controller = new AbortController() 168 | // Fetch the image 169 | fetch(url, { signal }).then(response => { 170 | // Get image URL, and if it was downloaded before, load another image 171 | // Otherwise, save image URL and set the texture 172 | const id = response.url.split('?')[0] 173 | if (imagesUrls[id]) { 174 | loadTextureForImage(index) 175 | } else { 176 | imagesUrls[id] = true 177 | image.texture = PIXI.Texture.from(response.url) 178 | rect.loaded = true 179 | } 180 | }).catch(() => { 181 | // Catch errors silently, for not showing the following error message if it is aborted: 182 | // AbortError: The operation was aborted. 183 | }) 184 | } 185 | 186 | // Add solid rectangles and images 187 | function initRectsAndImages () { 188 | // Create a new Graphics element to draw solid rectangles 189 | const graphics = new PIXI.Graphics() 190 | // Select the color for rectangles 191 | graphics.beginFill(0x000000) 192 | // Loop over each rect in the list 193 | rects.forEach(rect => { 194 | // Create a new Sprite element for each image 195 | const image = new PIXI.Sprite() 196 | // Set image's position and size 197 | image.x = rect.x * gridSize 198 | image.y = rect.y * gridSize 199 | image.width = rect.w * gridSize - imagePadding 200 | image.height = rect.h * gridSize - imagePadding 201 | // Set it's alpha to 0, so it is not visible initially 202 | image.alpha = 0 203 | // Add image to the list 204 | images.push(image) 205 | // Draw the rectangle 206 | graphics.drawRect(image.x, image.y, image.width, image.height) 207 | }) 208 | // Ends the fill action 209 | graphics.endFill() 210 | // Add the graphics (with all drawn rects) to the container 211 | container.addChild(graphics) 212 | // Add all image's Sprites to the container 213 | images.forEach(image => { 214 | container.addChild(image) 215 | }) 216 | } 217 | 218 | // Check if rects intersects with the viewport 219 | // and loads corresponding image 220 | function checkRectsAndImages () { 221 | // Loop over rects 222 | rects.forEach((rect, index) => { 223 | // Get corresponding image 224 | const image = images[index] 225 | // Check if the rect intersects with the viewport 226 | if (rectIntersectsWithViewport(rect)) { 227 | // If rect just has been discovered 228 | // start loading image 229 | if (!rect.discovered) { 230 | rect.discovered = true 231 | loadTextureForImage(index) 232 | } 233 | // If image is loaded, increase alpha if possible 234 | if (rect.loaded && image.alpha < 1) { 235 | image.alpha += 0.01 236 | } 237 | } else { // The rect is not intersecting 238 | // If the rect was discovered before, but the 239 | // image is not loaded yet, abort the fetch 240 | if (rect.discovered && !rect.loaded) { 241 | rect.discovered = false 242 | rect.controller.abort() 243 | } 244 | // Decrease alpha if possible 245 | if (image.alpha > 0) { 246 | image.alpha -= 0.01 247 | } 248 | } 249 | }) 250 | } 251 | 252 | // Check if a rect intersects the viewport 253 | function rectIntersectsWithViewport (rect) { 254 | return ( 255 | rect.x * gridSize + container.x <= width && 256 | 0 <= (rect.x + rect.w) * gridSize + container.x && 257 | rect.y * gridSize + container.y <= height && 258 | 0 <= (rect.y + rect.h) * gridSize + container.y 259 | ) 260 | } 261 | 262 | // Start listening events 263 | function initEvents () { 264 | // Make stage interactive, so it can listen to events 265 | app.stage.interactive = true 266 | 267 | // Pointer & touch events are normalized into 268 | // the `pointer*` events for handling different events 269 | app.stage 270 | .on('pointerdown', onPointerDown) 271 | .on('pointerup', onPointerUp) 272 | .on('pointerupoutside', onPointerUp) 273 | .on('pointermove', onPointerMove) 274 | } 275 | 276 | // On pointer down, save coordinates and set pointerDownTarget 277 | function onPointerDown (e) { 278 | const { x, y } = e.data.global 279 | pointerDownTarget = 1 280 | pointerStart.set(x, y) 281 | pointerDiffStart = uniforms.uPointerDiff.clone() 282 | } 283 | 284 | // On pointer up, set pointerDownTarget 285 | function onPointerUp () { 286 | pointerDownTarget = 0 287 | } 288 | 289 | // On pointer move, calculate coordinates diff 290 | function onPointerMove (e) { 291 | const { x, y } = e.data.global 292 | if (pointerDownTarget) { 293 | diffX = pointerDiffStart.x + (x - pointerStart.x) 294 | diffY = pointerDiffStart.y + (y - pointerStart.y) 295 | diffX = diffX > 0 ? Math.min(diffX, centerX + imagePadding) : Math.max(diffX, -(centerX + widthRest)) 296 | diffY = diffY > 0 ? Math.min(diffY, centerY + imagePadding) : Math.max(diffY, -(centerY + heightRest)) 297 | } 298 | } 299 | 300 | // Init everything 301 | function init () { 302 | initDimensions() 303 | initUniforms() 304 | initGrid() 305 | initApp() 306 | initBackground() 307 | initContainer() 308 | initRectsAndImages() 309 | initEvents() 310 | 311 | // Animation loop 312 | // Code here will be executed on every animation frame 313 | app.ticker.add(() => { 314 | // Multiply the values by a coefficient to get a smooth animation 315 | uniforms.uPointerDown += (pointerDownTarget - uniforms.uPointerDown) * 0.075 316 | uniforms.uPointerDiff.x += (diffX - uniforms.uPointerDiff.x) * 0.2 317 | uniforms.uPointerDiff.y += (diffY - uniforms.uPointerDiff.y) * 0.2 318 | // Set position for the container 319 | container.x = uniforms.uPointerDiff.x - centerX 320 | container.y = uniforms.uPointerDiff.y - centerY 321 | // Check rects and load/cancel images as needded 322 | checkRectsAndImages() 323 | }) 324 | } 325 | 326 | // Clean the current Application 327 | function clean () { 328 | // Stop the current animation 329 | app.ticker.stop() 330 | 331 | // Remove event listeners 332 | app.stage 333 | .off('pointerdown', onPointerDown) 334 | .off('pointerup', onPointerUp) 335 | .off('pointerupoutside', onPointerUp) 336 | .off('pointermove', onPointerMove) 337 | 338 | // Abort all fetch calls in progress 339 | rects.forEach(rect => { 340 | if (rect.discovered && !rect.loaded) { 341 | rect.controller.abort() 342 | } 343 | }) 344 | } 345 | 346 | // On resize, reinit the app (clean and init) 347 | // But first debounce the calls, so we don't call init too often 348 | let resizeTimer 349 | function onResize () { 350 | if (resizeTimer) clearTimeout(resizeTimer) 351 | resizeTimer = setTimeout(() => { 352 | clean() 353 | init() 354 | }, 200) 355 | } 356 | // Listen to resize event 357 | window.addEventListener('resize', onResize) 358 | 359 | // Load resources, then init the app 360 | PIXI.Loader.shared.add([ 361 | 'shaders/stageFragment.glsl', 362 | 'shaders/backgroundFragment.glsl' 363 | ]).load(init) 364 | 365 | })() 366 | --------------------------------------------------------------------------------