<!-- 
I created an advanced critterpedia for Animal Crossing New Horizons.

And this is a PoC about the fish shadow preview I built inside the fish detail page.

To see the whole app, please visit:

http://critterpedia-plus.mutoo.im/

-->
<div id="canvas" />
html, body {
   width: 100%;
   height: 100%;
}

body {
   display: flex;
   align-items: center;
   justify-content: center;
}

#canvas {
   border-radius: 50px;
   overflow: hidden;
}
View Compiled
const fishShadowImg =
   "https://raw.githubusercontent.com/mutoo/critterpedia-plus/master/app/assets/images/fish-shadow.png";
const fishShadowImgWithFin =
   "https://raw.githubusercontent.com/mutoo/critterpedia-plus/master/app/assets/images/fish-shadow-fin.png";
const waterImg =
   "https://raw.githubusercontent.com/mutoo/critterpedia-plus/master/app/assets/images/water.png";

const app = new PIXI.Application({ width: 300, height: 300 });
const canvas = document.getElementById("canvas");
canvas.appendChild(app.view);

const gui = new dat.GUI({
   load: {
      preset: "Default",
      remembered: {
         Default: { "0": {} },
         "smallest (1)": {
            "0": {
               segment: 20,
               length: 30,
               fin: false,
               thickness: 12,
               curvedness: 0.3,
               amp: 5
            }
         },
         "small (2)": {
            "0": {
               segment: 20,
               length: 50,
               fin: false,
               thickness: 22,
               curvedness: 0.3,
               amp: 5
            }
         },
         "medium (3)": {
            "0": {
               segment: 20,
               length: 70,
               fin: false,
               thickness: 30,
               curvedness: 0.2,
               amp: 10
            }
         },
         "medium (4)": {
            "0": {
               segment: 20,
               length: 90,
               fin: false,
               thickness: 35,
               curvedness: 0.2,
               amp: 10
            }
         },
         "large (5)": {
            "0": {
               segment: 20,
               length: 100,
               fin: false,
               thickness: 40,
               curvedness: 0.2,
               amp: 10
            }
         },
         "largest (6)": {
            "0": {
               segment: 20,
               length: 120,
               fin: false,
               thickness: 40,
               curvedness: 0.35,
               amp: 10
            }
         },
         "largest (6) with fin": {
            "0": {
               segment: 20,
               length: 120,
               fin: true,
               thickness: 40,
               curvedness: 0.35,
               amp: 10
            }
         },
         narrow: {
            "0": {
               segment: 20,
               length: 130,
               fin: false,
               thickness: 20,
               curvedness: 2,
               amp: 10
            }
         }
      },
      closed: false,
      folders: {}
   }
});
const config = {
   segment: 20,
   thickness: 40,
   curvedness: 0.35,
   amp: 10,
   length: 120,
   fin: true
};
const segmentCtrl = gui.add(config, "segment", 3, 50);
const lenCtrl = gui.add(config, "length", 20, 150);
const finCtrl = gui.add(config, "fin");
const thickCtrl = gui.add(config, "thickness", 10, 50);
gui.add(config, "curvedness", 0.1, 2);
gui.add(config, "amp", 1, 30);
gui.remember(config);

const water = PIXI.Sprite.from(waterImg);
water.anchor.set(0.5);
water.x = app.screen.width / 2;
water.y = app.screen.height / 2;
app.stage.addChild(water);

let fishShadow;
let fishShadowPts;
function setupFishShadow() {
   if (fishShadow) app.stage.removeChild(fishShadow);
   fishShadowPts = [];
   for (let i = 0; i < config.segment; i++) {
      fishShadowPts.push(
         new PIXI.Point((i / config.segment) * config.length, 0)
      );
   }
   fishShadow = new PIXI.SimpleRope(
      PIXI.Texture.from(config.fin ? fishShadowImgWithFin : fishShadowImg),
      fishShadowPts
   );
   fishShadow.scale.y = config.thickness / 30;
   fishShadow.x = app.screen.width / 2 - config.length / 2;
   fishShadow.y = app.screen.height / 2;
   app.stage.addChild(fishShadow);
}

setupFishShadow();

segmentCtrl.onFinishChange((val) => setupFishShadow());
lenCtrl.onFinishChange((val) => setupFishShadow());
finCtrl.onFinishChange((val) => setupFishShadow());
thickCtrl.onFinishChange((val) => setupFishShadow());

let timelapse = 0;
app.ticker.add((delta) => {
   timelapse += delta;
   fishShadowPts?.forEach((p, idx) => {
      // the fish tail animail composed by two basic functions:
      // https://www.desmos.com/calculator/0cmkc1w148
      const i = idx / config.segment;
      p.y =
         i ** 2 *
         Math.sin(timelapse / 10 - i * 2 * Math.PI * config.curvedness) *
         config.amp;
   });
});
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.1.3/pixi.min.js
  2. https://unpkg.com/dat.gui