Frontend Development 13 min read

Liquid Flow Effects with SVG, CSS Masking, and Canvas – A Frontend Development Tutorial

This article demonstrates how to create dynamic liquid‑flow animations for static images using SVG filters (feTurbulence, feDisplacementMap), CSS mask‑image, Canvas drawing, and TweenMax, providing step‑by‑step code for generating hotspots, animating them, and integrating image upload and canvas clearing in a web page.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Liquid Flow Effects with SVG, CSS Masking, and Canvas – A Frontend Development Tutorial

The article explains the background of liquid‑flow visual effects commonly seen in mobile games such as "Honor of Kings" and "Onmyoji", where static backgrounds are enhanced with localized fluid animations to reduce development cost and increase visual appeal.

It introduces a front‑end implementation that combines SVG filters ( feTurbulence and feDisplacementMap ), CSS mask-image , the filter property, Canvas drawing, TimelineMax animation, and input[type=file] for local image loading.

Effect Demonstration

Several example animations (mist diffusion, flag waving, character sleeves, lake ripples, text liquefaction) are shown, with a link to an online demo where users can experience the effect and generate their own animated skins.

Implementation Overview

The page consists of two main parts: a top area for loading an image and drawing a hotspot path with mouse dragging, and a bottom control panel with buttons to clear the canvas and switch images.

HTML Structure

<main id="sketch">
  <canvas id="canvas" data-img=""></canvas>
  <div class="mask">
    <div id="maskInner" class="mask-inner"></div>
  </div>
</main>
<section class="button_container">
  <button class="button">Clear Canvas</button>
  <button class="button"><input class="input" type="file" id="upload">Upload Image</button>
</section>
<svg>
  <filter id="heat" filterUnits="objectBoundingBox" x="0" y="0" width="100%" height="100%">
    <feTurbulence id="heatturb" type="fractalNoise" numOctaves="1" seed="2"/>
    <feDisplacementMap xChannelSelector="G" yChannelSelector="B" scale="22" in="SourceGraphic"/>
  </filter>
</svg>

CSS Styles

main {
  position: relative;
  background-image: url('bg.jpg');
  background-size: cover;
  background-position: 100% 50%;
}
canvas {
  opacity: 0;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
.mask {
  display: none;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  mask-mode: luminance;
  mask-size: 100% 100%;
  backdrop-filter: hard-light;
  mask-image: url('mask.png');
}
.mask-inner {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: url('bg.jpg') 0% 0% repeat;
  background-size: cover;
  background-position: 100% 50%;
  filter: url(#heat);
  mask-image: url('mask.png');
}

The mask-image property creates a mask that blends the generated hotspot map with the background image, while the filter applies the SVG filter to produce the liquid distortion.

JavaScript Methods

1. Drawing the Hotspot Map

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var sketch = document.getElementById('sketch');
var sketchStyle = window.getComputedStyle(sketch);
var mouse = { x: 0, y: 0 };

canvas.width = parseInt(sketchStyle.getPropertyValue('width'));
canvas.height = parseInt(sketchStyle.getPropertyValue('height'));
canvas.addEventListener('mousemove', e => {
  mouse.x = e.pageX - canvas.getBoundingClientRect().left;
  mouse.y = e.pageY - canvas.getBoundingClientRect().top;
}, false);

ctx.lineWidth = 40;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.strokeStyle = 'black';

canvas.addEventListener('mousedown', () => {
  ctx.beginPath();
  ctx.moveTo(mouse.x, mouse.y);
  canvas.addEventListener('mousemove', onPaint, false);
}, false);

canvas.addEventListener('mouseup', () => {
  canvas.removeEventListener('mousemove', onPaint, false);
}, false);

var onPaint = () => {
  ctx.lineTo(mouse.x, mouse.y);
  ctx.stroke();
  var url = canvas.toDataURL();
  document.querySelectorAll('div').forEach(item => {
    item.style.cssText += `
      display: initial;
      -webkit-mask-image: url(${url});
      mask-image: url(${url});
    `;
  });
};

2. Generating the Animation

feTurb = document.querySelector('#heatturb');
var timeline = new TimelineMax({
  repeat: -1,
  yoyo: true
});

timeline.add(
  new TweenMax.to(feTurb, 8, {
    onUpdate: () => {
      var bfX = this.progress() * 0.01 + 0.025,
          bfY = this.progress() * 0.003 + 0.01,
          bfStr = bfX.toString() + ' ' + bfY.toString();
      feTurb.setAttribute('baseFrequency', bfStr);
    }
  }),
0);

The animation continuously updates the baseFrequency of feTurbulence to create a flowing effect; the same result can be achieved with other animation libraries or requestAnimationFrame .

3. Clearing the Canvas

function clear() {
  document.querySelectorAll('div').forEach(item => {
    item.style.cssText += `
      display: none;
      -webkit-mask-image: none;
      mask-image: none;
    `;
  });
}

document.querySelectorAll('.button').forEach(item => {
  item.addEventListener('click', () => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    clear();
  })
});

4. Switching Images

document.getElementById('upload').onchange = function() {
  var imageFile = this.files[0];
  var newImg = window.URL.createObjectURL(imageFile);
  clear();
  document.getElementById('sketch').style.cssText += `
    background: url(${newImg});
    background-size: cover;
    background-position: center;
  `;
  document.getElementById('maskInner').style.cssText += `
    background: url(${newImg});
    background-size: cover;
    background-position: center;
  `;
};

After implementing all four functions, users can draw custom hotspot paths on any image, animate the liquid flow, clear the effect, or load a new picture, enabling the creation of epic skin launch pages or mini‑games.

Summary of New Knowledge Points

CSS mask-image for masking elements.

SVG filters feTurbulence and feDisplacementMap for liquid distortion.

CSS filter property to apply SVG filters.

Canvas drawing techniques for hotspot generation.

TimelineMax animation of SVG filter attributes.

Using input[type=file] to load local images.

frontendanimationCanvasSVGCSSmasking
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.