使用前端技術實現靜態圖片局部流動效果

聲明:本文涉及圖文和模型素材僅用於個人學習、研究和欣賞,請勿二次修改、非法傳播、轉載、出版、商用、及進行其他獲利行爲。

背景

如果你有玩過 🎮 《王者榮耀》《陰陽師》 等手遊,一定注意到過它的啓動動畫、皮膚立繪卡片等場景,經常採用靜態底圖加局部液態流動效果的簡單動畫,這些流動動畫可能出現在緩緩流動的水流 🌊、迎風飄動的旗幟 🎏、遊戲角色衣袖 🧜♀️、隨着時間緩動的雲、雨、霧天氣效果 等。這種過渡效果不僅節省了開發全量動畫的成本,而且使得遊戲畫面更加熱血、冒險、奧德賽、高級,也更加容易吸引玩家氪金 💰

本文使用前端開發技術,結合 SVGCSS 來實現類似的液化流動效果。本文包含的知識點主要包括:mask-image 遮罩、feTurbulencefeDisplacementMap 濾鏡、filter 屬性、canvas 繪製方法、TimelineMax 動畫以及input[type=file] 本地圖片資源加載等。

效果

先來看看實現效果,下面幾個示例以及 👆 文章 Banner 圖都是應用了由本文內容生成的液態流動動畫效果。由於GIF 圖壓縮比較嚴重,動畫效果看起來不是很流暢 🙃,大家不妨通過以下演示頁面鏈接,親自體驗一下效果,生成自己的 傳說典藏 皮膚立繪吧 😅

👁🗨 在線體驗:dragonir.github.io/paint-heat-…[1]

🌀 霧氣擴散 塞爾達傳說:曠野之息

💃 衣袖飄動 貂蟬:貓影幻舞

🌅 湖光波動

🔠 文字液化

📌 ps:體驗頁面部署在 Gitpage 上傳圖片功能不是真正上傳到服務器,而是隻會加載到瀏覽器本地,頁面不會獲取任何信息,大家可以放心體驗,不用擔心隱私泄漏問題。

碼上掘金

實現

頁面主要由 2 部分構成,頂部用於加載圖片 ,並且可以通過按住 🖱 鼠標划動的方式繪製熱點路徑,給圖片添加流動效果;底部是控制區域,點擊按鈕 🔘 清除畫布,可以清除繪製的流動動畫效果、點擊按鈕 🔘 切換圖片可以加載本地的圖片。

📌 注意,還有一個隱形的功能,當你繪製完成時,可以點擊 🖱 鼠標右鍵,然後選擇保存圖片,保存的這張圖片就是我們繪製流體動畫路徑的熱點圖,利用這張熱點圖,使用本文的 CSS 知識,就能把靜態圖片轉化成動態圖啦!

HTML 頁面結構

#sketch 元素主要是用於繪製和加載流動效果熱點圖的畫板;#button_container 是頁面底部的按鈕控制區域;svg 元素用於利用其 filter 濾鏡實現液態流動動畫效果,包括 feTurbulencefeDisplacementMap 濾鏡。

<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">清除畫布</button>
  <button class="button"><input class="input" type="file" id="upload">上傳圖片</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>
複製代碼

💡 feTurbulence 和 feDisplacementMap

CSS 樣式

接着看看樣式的實現,main 元素作爲主容器並將主圖案作爲背景圖片;canvas 作爲畫布佔據 100% 的空間位置;.mask.mask-inner 用於生成如下圖所示熱點路徑與背景圖相溶的效果,這種效果是藉助 mask-image 實現的。最後,爲了生成動態流動效果,.mask-inner 通過 filter: url(#heat) 將前面生成的 svg 作爲濾鏡來源,後續即將在 JavaScript 中通過不間斷修改 svg 濾鏡的屬性,來生成液態流動動畫。

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')
}
複製代碼

💡 mask-image

mask-image CSS 屬性用於設置元素上遮罩層的圖像。

語法

// 默認值,透明的黑色圖像層,也就是沒有遮罩層。
mask-image: none;
// <mask-source><mask>或CSS圖像的url的值
mask-image: url(masks.svg#mask1);
// <image> 圖片作爲遮罩層
mask-image: linear-gradient(rgba(0, 0, 0, 1.0), transparent);
mask-image: image(url(mask.png), skyblue);
// 多個值
mask-image: image(url(mask.png), skyblue), linear-gradient(rgba(0, 0, 0, 1.0), transparent);
// 全局值
mask-image: inherit;
mask-image: initial;
mask-image: unset;
複製代碼

兼容性

此功能某些瀏覽器尚在開發中,需要使用瀏覽器前綴以兼容不同瀏覽器。

JavaScript 方法

① 繪製熱點圖

監聽鼠標移動和點擊事件,在 canvas 上繪製波動路徑熱點。

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});
    `;
  });
};
複製代碼

繪製完成後,可以在頁面中右鍵保存生成的波動路徑熱點圖,直接將繪製滿意的熱點圖放到 CSS 中,就能給喜歡的圖片添加局部波動效果了,下面這張圖片就是本示例頁面使用的波動的熱點路徑圖。

② 生成動畫

爲了生成實時更新的波動效果,本文使用了 TweenMax 來通過改變 feTurbulencebaseFrequency 屬性值來實現,使用其他動畫庫或使用 requestAnimationFrame 也是可以實現相同的功能。

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);
複製代碼

③ 清除畫布

點擊清除畫布按鈕,可以清空已經繪製的波動路徑,主要是通過清除頁面元素 mask-image 的屬性值以及清 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();
  })
});
複製代碼

④ 切換圖片

點擊切換圖片,可以加載本地的一張圖片作爲繪製底圖,該功能是通過 input[type=file] 來實現圖片資源的獲取,然後通過修改 CSS 將它設置成新的畫布背景。

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;
  `;
};
複製代碼

到這裏,全部功能都實現完畢了,大家趕快製作一張自己喜歡的 史詩皮膚奧德賽小遊戲 的啓動頁面吧 🤣

📥 源碼地址:github.com/dragonir/pa…[2]

總結

本文包含的新知識點主要包括:

想了解其他前端知識或其他未在本文中詳細描述的 Web 3D 開發技術相關知識,可閱讀我往期的文章。轉載請註明原文地址和作者。如果覺得文章對你有幫助,不要忘了一鍵三連哦 👍

附錄

參考

參考資料

[1]

https://dragonir.github.io/paint-heat-map/: https://link.juejin.cn/?target=https%3A%2F%2Fdragonir.github.io%2Fpaint-heat-map%2F

[2]

https://github.com/dragonir/paint-heat-map: https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fdragonir%2Fpaint-heat-map

[3]

https://juejin.cn/column/7049923956257587213: https://juejin.cn/column/7049923956257587213

[4]

https://juejin.cn/post/7124116814937718797#comment: https://juejin.cn/post/7124116814937718797#comment

[5]

https://juejin.cn/post/7081429595689320478: https://juejin.cn/post/7081429595689320478

[6]

https://juejin.cn/post/7077726955528781832: https://juejin.cn/post/7077726955528781832

[7]

https://juejin.cn/post/7060292943608807460: https://juejin.cn/post/7060292943608807460

[8]

https://juejin.cn/post/7018722520345870350: https://juejin.cn/post/7018722520345870350

[9]

https://juejin.cn/post/7007432493569671182: https://juejin.cn/post/7007432493569671182

[10]

https://juejin.cn/post/6972759988632551460: https://juejin.cn/post/6972759988632551460

[11]

https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/feTurbulence: https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FSVG%2FElement%2FfeTurbulence

[12]

https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/feDisplacementMap: https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FSVG%2FElement%2FfeDisplacementMap

[13]

https://developer.mozilla.org/zh-CN/docs/Web/CSS/mask-image: https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FCSS%2Fmask-image

[14]

https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter: https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FCSS%2Ffilter

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/GrlAFjVShoN-RtBBjc21xw