<canvas class="view"></canvas>

<script id="backgroundFragment" type="x-shader/x-fragment">
  // Adapted from: https://www.shadertoy.com/view/MdlGRr

  // It is required to set the float precision for fragment shaders in OpenGL ES
  // More info here: https://stackoverflow.com/a/28540641/4908989
  #ifdef GL_ES
  precision mediump float;
  #endif

  // This function returns 1 if `coord` correspond to a grid line, 0 otherwise
  float isGridLine (vec2 coord) {
    vec2 pixelsPerGrid = vec2(50.0, 50.0);
    vec2 gridCoords = fract(coord / pixelsPerGrid);
    vec2 gridPixelCoords = gridCoords * pixelsPerGrid;
    vec2 gridLine = step(gridPixelCoords, vec2(1.0));
    float isGridLine = max(gridLine.x, gridLine.y);
    return isGridLine;
  }

  // Main function
  void main () {
    // Coordinates for the current pixel
    vec2 coord = gl_FragCoord.xy;
    // Set `color` to black
    vec3 color = vec3(0.0);
    // If it is a grid line, change blue channel to 0.3
    color.b = isGridLine(coord) * 0.3;
    // Assing the final rgba color to `gl_FragColor`
    gl_FragColor = vec4(color, 1.0);
  }
</script>

<script id="stageFragment" type="x-shader/x-fragment">
  // Adapted from: https://www.shadertoy.com/view/4lSGRw

  #ifdef GL_ES
  precision mediump float;
  #endif

  // Uniforms from Javascript
  uniform vec2 uResolution;
  uniform float uPointerDown;

  // The texture is defined by PixiJS
  varying vec2 vTextureCoord;
  uniform sampler2D uSampler;

  // Function used to get the distortion effect
  vec2 computeUV (vec2 uv, float k, float kcube) {
    vec2 t = uv - 0.5;
    float r2 = t.x * t.x + t.y * t.y;
    float f = 0.0;
    if (kcube == 0.0) {
      f = 1.0 + r2 * k;
    } else {
      f = 1.0 + r2 * (k + kcube * sqrt(r2));
    }
    vec2 nUv = f * t + 0.5;
    nUv.y = 1.0 - nUv.y;
    return nUv;
  }

  void main () {
    // Normalized coordinates
    vec2 uv = gl_FragCoord.xy / uResolution.xy;

    // Settings for the effect
    // Multiplied by `uPointerDown`, a value between 0 and 1
    float k = -1.0 * uPointerDown;
    float kcube = 0.5 * uPointerDown;
    float offset = 0.02 * uPointerDown;

    // Get each channel's color using the texture provided by PixiJS
    // and the `computeUV` function
    float red = texture2D(uSampler, computeUV(uv, k + offset, kcube)).r;
    float green = texture2D(uSampler, computeUV(uv, k, kcube)).g;
    float blue = texture2D(uSampler, computeUV(uv, k - offset, kcube)).b;

    // Assing the final rgba color to `gl_FragColor`
    gl_FragColor = vec4(red, green, blue, 1.0);
  }
</script>
body {
  margin: 0;
  overflow: hidden;
  background-color: black;
}
(function() {

  // Get canvas view
  const view = document.querySelector('.view')
  // Loaded resources will be here
  const resources = PIXI.Loader.shared.resources
  // Target for pointer. If down, value is 1, else value is 0
  // Here we set it to 1 to see the effect, but initially it will be 0
  let pointerDownTarget = 1
  let width, height, app, background, uniforms

  // Set dimensions
  function initDimensions () {
    width = window.innerWidth
    height = window.innerHeight
  }

  // Set initial values for uniforms
  function initUniforms () {
    uniforms = {
      uResolution: new PIXI.Point(width, height),
      uPointerDown: pointerDownTarget
    }
  }

  // Init the PixiJS Application
  function initApp () {
    // Create a PixiJS Application, using the view (canvas) provided
    app = new PIXI.Application({ view })
    // Resizes renderer view in CSS pixels to allow for resolutions other than 1
    app.renderer.autoDensity = true
    // Resize the view to match viewport dimensions
    app.renderer.resize(width, height)

    // Set the distortion filter for the entire stage
    const stageFragmentShader = document.getElementById('stageFragment').textContent
    const stageFilter = new PIXI.Filter(undefined, stageFragmentShader, uniforms)
    app.stage.filters = [stageFilter]
  }

  // Init the gridded background
  function initBackground () {
    // Create a new empty Sprite and define its size
    background = new PIXI.Sprite()
    background.width = width
    background.height = height
    // Get the code for the fragment shader from the loaded resources
    const backgroundFragmentShader = document.getElementById('backgroundFragment').textContent
    // Create a new Filter using the fragment shader
    // We don't need a custom vertex shader, so we set it as `undefined`
    const backgroundFilter = new PIXI.Filter(undefined, backgroundFragmentShader)
    // Assign the filter to the background Sprite
    background.filters = [backgroundFilter]
    // Add the background to the stage
    app.stage.addChild(background)
  }

  // Init everything
  function init () {
    initDimensions()
    initUniforms()
    initApp()
    initBackground()
  }

  // Init the app
  init()

})()

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.0.2/pixi.min.js