body {
  margin: 0;
}

canvas {
  position: absolute;
}
const frag = `
precision highp float;

varying vec2 vTextureCoord;
uniform vec4 inputSize;

uniform sampler2D uT0;
uniform vec2 uT0_Size;

uniform sampler2D uT1;
uniform vec2 uT1_Size;

uniform float uProgress;
uniform float uStrokeWidth;

float hash21(vec2 p) {
  p = fract(p*vec2(1.34, 435.345));
  p += dot(p, p+34.23);
  return fract(p.x*p.y);
}

vec2 coverUV(vec2 planeSize, vec2 imageSize, vec2 texCoodr, vec2 pivot) {
  vec2 ratio = planeSize / imageSize;
  vec2 scale = ratio / max(ratio.x, ratio.y);
  return (texCoodr - pivot) * scale + pivot;
}

void main(void) {
  vec2 grid = vec2(6.0, 4.0);
  
  vec2 gUV = fract(vTextureCoord.xy * grid);
  vec2 gID = floor(vTextureCoord.xy * grid);
  
  float offs = abs(1.0 + hash21(gID));
  float progress = clamp(mix(-offs, 1.0 + offs, uProgress), 0.0, 1.0);
  float angle = progress * 3.1415926;
  
  float x = (gUV.x - 0.5) / cos(angle) + 0.5;
  float z = (x - 0.5) * sin(angle);
  float sy = mix(1.0, 0.35, z);
  float y = (gUV.y - 0.5) / sy + 0.5;
  vec2 cellUV = vec2(x, y);
  
  if (progress > 0.5) cellUV.x = 1.0 - cellUV.x;
  
  vec2 visible = step(abs(cellUV - 0.5), vec2(0.5) + inputSize.zw * 0.5);
  float mask = min(visible.x, visible.y);
  
  float sp = mix(-1.0, 2.0, uProgress);
  if (sp < 0.0) sp = mix(1.0, 0.9, abs(sp));
  else if (sp < 1.0) sp = 1.0;
  else sp = mix(1.0, 0.9, sp - 1.0);
  
  vec2 uv = (gID + cellUV) / grid;
  uv = (uv - 0.5) * sp + 0.5;
  vec2 uv0 = coverUV(inputSize.xy, uT0_Size.xy, uv, vec2(0.5));
  vec2 uv1 = coverUV(inputSize.xy, uT1_Size.xy, uv, vec2(0.5));
  
  vec2 lw = uStrokeWidth * (0.5 - abs(progress - 0.5)) * inputSize.zw * grid * 0.5;
  vec2 line = step(abs(gUV - 0.5), vec2(0.5) - lw + inputSize.zw * 0.5);
  float lines = 1.0 - min(line.x, line.y);
  vec4 lineColor = vec4(0.15, 0.15, 0.15, 0.75) * lines;

  vec4 t0 = texture2D(uT0, uv0);
  vec4 t1 = texture2D(uT1, uv1);
  vec4 tex = mix(t0, t1, step(0.5, progress));
  vec4 color = mix(vec4(0.0), tex, mask);
  gl_FragColor = vec4(mix(color.rgb, lineColor.rgb, lineColor.a), 1.0);
}`;

const app = new PIXI.Application({
  autoStar: false,
  resizeTo: window
});
document.body.appendChild(app.view);

const background = PIXI.Sprite.from(PIXI.Texture.WHITE);
background.width = app.screen.width;
background.height = app.screen.height;
app.stage.addChild(background);

const filter = new PIXI.Filter(null, frag, {
  uProgress: 0,
  uStrokeWidth: 10,
  uT0: PIXI.Texture.WHITE,
  uT0_Size: [1, 1],
  uT1: PIXI.Texture.WHITE,
  uT1_Size: [1, 1],
});

background.filters = [filter];

let time = 0;
app.ticker.add((delta) => {
  time += delta;
  let p = time / 60 % 10 / 10;
  p = 1 - Math.abs(p - 0.5) * 2;
  filter.uniforms.uProgress = p;
  
  background.width = app.screen.width;
  background.height = app.screen.height;
});

const loaderImageOptions = {
  loadType: PIXI.LoaderResource.LOAD_TYPE.IMAGE,
};

PIXI.Loader.shared
  .add('img0', 'https://images.unsplash.com/photo-1526336024174-e58f5cdd8e13?ixid=MXwxMjA3fDB8MHxzZWFyY2h8NXx8Y2F0fGVufDB8fDB8&ixlib=rb-1.2.1&auto=format&fit=crop&w=640&q=80', loaderImageOptions)
  .add('img1', 'https://images.unsplash.com/photo-1569591159212-b02ea8a9f239?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=640&q=80', loaderImageOptions)
  .load((loader, resources) => { 
    const tex0 = resources.img0.texture;
    filter.uniforms.uT0 = tex0;
    filter.uniforms.uT0_Size = [tex0.baseTexture.width, tex0.baseTexture.height];

    const tex1 = resources.img1.texture;
    filter.uniforms.uT1 = tex1;
    filter.uniforms.uT1_Size = [tex1.baseTexture.width, tex1.baseTexture.height];
  
    app.start();
  });

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

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