经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » JavaScript » 查看文章
利用JavaScript实现静态图片局部流动效果
来源:jb51  时间:2022/8/3 13:18:27  对本文有异议

背景

如果你有玩过《王者荣耀》、《阴阳师》 等手游,一定注意到过它的启动动画、皮肤立绘卡片等场景,经常采用静态底图加局部液态流动效果的简单动画,这些流动动画可能出现在缓缓流动的水流 、迎风飘动的旗帜 、游戏角色衣袖 、随着时间缓动的云、雨、雾天气效果  等。这种过渡效果不仅节省了开发全量动画的成本,而且使得游戏画面更加热血、冒险、奥德赛、高级,也更加容易吸引玩家氪金 。

本文使用前端开发技术,结合 SVG 和 CSS 来实现类似的液化流动效果。本文包含的知识点主要包括:mask-image 遮罩、feTurbulence 和 feDisplacementMap 滤镜、filter 属性、canvas 绘制方法、TimelineMax 动画以及input[type=file] 本地图片资源加载等。

效果

先来看看实现效果,下面几个示例以及文章 Banner 图都是应用了由本文内容生成的液态流动动画效果。由于GIF 图压缩比较严重,动画效果看起来不是很流畅 ,大家不妨通过以下演示页面链接,亲自体验一下效果,生成自己的 传说、典藏 皮肤立绘吧 。

雾气扩散 塞尔达传说:旷野之息

衣袖飘动 貂蝉:猫影幻舞

湖光波动

文字液化

ps:体验页面部署在 Gitpage 上传图片功能不是真正上传到服务器,而是只会加载到浏览器本地,页面不会获取任何信息,大家可以放心体验,不用担心隐私泄漏问题。

实现

页面主要由2部分构成,顶部用于加载图片 ,并且可以通过按住鼠标划动的方式绘制热点路径,给图片添加流动效果;底部是控制区域,点击按钮清除画布,可以清除绘制的流动动画效果、点击按钮切换图片可以加载本地的图片。

注意,还有一个隐形的功能,当你绘制完成时,可以点击鼠标右键,然后选择保存图片,保存的这张图片就是我们绘制流体动画路径的热点图,利用这张热点图,使用本文的 CSS 知识,就能把静态图片转化成动态图啦!

HTML 页面结构

#sketch 元素主要是用于绘制和加载流动效果热点图的画板;#button_container 是页面底部的按钮控制区域;svg 元素用于利用其 filter 滤镜实现液态流动动画效果,包括 feTurbulence 和 feDisplacementMap 滤镜。

  1. <main id="sketch">
  2. <canvas id="canvas" data-img=""></canvas>
  3. <div class="mask">
  4. <div id="maskInner" class="mask-inner"></div>
  5. </div>
  6. </main>
  7. <section class="button_container">
  8. <button class="button">清除画布</button>
  9. <button class="button"><input class="input" type="file" id="upload">上传图片</button>
  10. </section>
  11. <svg>
  12. <filter id="heat" filterUnits="objectBoundingBox" x="0" y="0" width="100%" height="100%">
  13. <feTurbulence id="heatturb" type="fractalNoise" numOctaves="1" seed="2" />
  14. <feDisplacementMap xChannelSelector="G" yChannelSelector="B" scale="22" in="SourceGraphic" />
  15. </filter>
  16. </svg>

feTurbulence 和 feDisplacementMap

  • feTurbulence:滤镜利用 Perlin 噪声函数创建了一个图像,利用它可以实现人造纹理比如说云纹、大理石纹等模拟滤镜效果。
  • feDisplacementMap:映射置换滤镜,该滤镜用来自图像中从 in2 到空间的像素值置换图像从 in 到空间的像素值。即它可以改变元素和图形的像素位置,通过遍历原图形的所有像素点,feDisplacementMap 重新映射替换一个新的位置,形成一个新的图形。该滤镜在业界的主流应用是对图形进行形变,扭曲,液化。

CSS 样式

接着看看样式的实现,main 元素作为主容器并将主图案作为背景图片;canvas 作为画布占据 100% 的空间位置;.mask 和 .mask-inner 用于生成如下图所示热点路径与背景图相溶的效果,这种效果是借助 mask-image 实现的。最后,为了生成动态流动效果,.mask-inner 通过 filter: url(#heat) 将前面生成的 svg 作为滤镜来源,后续即将在 JavaScript 中通过不间断修改 svg 滤镜的属性,来生成液态流动动画。

  1. main {
  2. position: relative;
  3. background-image: url('bg.jpg');
  4. background-size: cover;
  5. background-position: 100% 50%;
  6. }
  7. canvas {
  8. opacity: 0;
  9. position: absolute;
  10. top: 0;
  11. left: 0;
  12. width: 100%;
  13. height: 100%;
  14. }
  15. .mask {
  16. display: none;
  17. position: absolute;
  18. top: 0;
  19. left: 0;
  20. width: 100%;
  21. height: 100%;
  22. mask-mode: luminance;
  23. mask-size: 100% 100%;
  24. backdrop-filter: hard-light;
  25. mask-image: url('mask.png');
  26. }
  27. .mask-inner {
  28. position: absolute;
  29. top: 0;
  30. left: 0;
  31. width: 100%;
  32. height: 100%;
  33. background: url('bg.jpg') 0% 0% repeat;
  34. background-size: cover;
  35. background-position: 100% 50%;
  36. filter: url(#heat);
  37. mask-image: url('mask.png')
  38. }

mask-image

mask-image CSS 属性用于设置元素上遮罩层的图像。

语法

  1. // 默认值,透明的黑色图像层,也就是没有遮罩层。
  2. mask-image: none;
  3. // <mask-source><mask>或CSS图像的url的值
  4. mask-image: url(masks.svg#mask1);
  5. // <image> 图片作为遮罩层
  6. mask-image: linear-gradient(rgba(0, 0, 0, 1.0), transparent);
  7. mask-image: image(url(mask.png), skyblue);
  8. // 多个值
  9. mask-image: image(url(mask.png), skyblue), linear-gradient(rgba(0, 0, 0, 1.0), transparent);
  10. // 全局值
  11. mask-image: inherit;
  12. mask-image: initial;
  13. mask-image: unset;

兼容性

此功能某些浏览器尚在开发中,需要使用浏览器前缀以兼容不同浏览器。

JavaScript 方法

① 绘制热点图

监听鼠标移动和点击事件,在 canvas 上绘制波动路径热点。

  1. var canvas = document.getElementById('canvas');
  2. var ctx = canvas.getContext('2d');
  3. var sketch = document.getElementById('sketch');
  4. var sketchStyle = window.getComputedStyle(sketch);
  5. var mouse = { x: 0, y: 0 };
  6.  
  7. canvas.width = parseInt(sketchStyle.getPropertyValue('width'));
  8. canvas.height = parseInt(sketchStyle.getPropertyValue('height'));
  9. canvas.addEventListener('mousemove', e => {
  10. mouse.x = e.pageX - canvas.getBoundingClientRect().left;
  11. mouse.y = e.pageY - canvas.getBoundingClientRect().top;
  12. }, false);
  13.  
  14. ctx.lineWidth = 40;
  15. ctx.lineJoin = 'round';
  16. ctx.lineCap = 'round';
  17. ctx.strokeStyle = 'black';
  18.  
  19. canvas.addEventListener('mousedown', () => {
  20. ctx.beginPath();
  21. ctx.moveTo(mouse.x, mouse.y);
  22. canvas.addEventListener('mousemove', onPaint, false);
  23. }, false);
  24.  
  25. canvas.addEventListener('mouseup', () => {
  26. canvas.removeEventListener('mousemove', onPaint, false);
  27. }, false);
  28.  
  29. var onPaint = () => {
  30. ctx.lineTo(mouse.x, mouse.y);
  31. ctx.stroke();
  32. var url = canvas.toDataURL();
  33. document.querySelectorAll('div').forEach(item => {
  34. item.style.cssText += `
  35. display: initial;
  36. -webkit-mask-image: url(${url});
  37. mask-image: url(${url});
  38. `;
  39. });
  40. };

绘制完成后,可以在页面中右键保存生成的波动路径热点图,直接将绘制满意的热点图放到 CSS 中,就能给喜欢的图片添加局部波动效果了,下面这张图片就是本示例页面使用的波动的热点路径图。

② 生成动画

为了生成实时更新的波动效果,本文使用了 TweenMax 来通过改变 feTurbulence 的 baseFrequency 属性值来实现,使用其他动画库或使用 requestAnimationFrame 也是可以实现相同的功能。

  1. feTurb = document.querySelector('#heatturb');
  2. var timeline = new TimelineMax({
  3. repeat: -1,
  4. yoyo: true
  5. }),
  6. timeline.add(
  7. new TweenMax.to(feTurb, 8, {
  8. onUpdate: () => {
  9. var bfX = this.progress() * 0.01 + 0.025,
  10. bfY = this.progress() * 0.003 + 0.01,
  11. bfStr = bfX.toString() + ' ' + bfY.toString();
  12. feTurb.setAttribute('baseFrequency', bfStr);
  13. }
  14. }),
  15. 0);

③ 清除画布

点击清除画布按钮,可以清空已经绘制的波动路径,主要是通过清除页面元素 mask-image 的属性值以及清 canvas 画布来实现的。

  1. function clear() {
  2. document.querySelectorAll('div').forEach(item => {
  3. item.style.cssText += `
  4. display: none;
  5. -webkit-mask-image: none;
  6. mask-image: none;
  7. `;
  8. });
  9. }
  10.  
  11. document.querySelectorAll('.button').forEach(item => {
  12. item.addEventListener('click', () => {
  13. ctx.clearRect(0, 0, canvas.width, canvas.height);
  14. clear();
  15. })
  16. });

④ 切换图片

点击切换图片,可以加载本地的一张图片作为绘制底图,该功能是通过 input[type=file] 来实现图片资源的获取,然后通过修改 CSS 将它设置成新的画布背景。

  1. document.getElementById('upload').onchange = function () {
  2. var imageFile = this.files[0];
  3. var newImg = window.URL.createObjectURL(imageFile);
  4. clear();
  5. document.getElementById('sketch').style.cssText += `
  6. background: url(${newImg});
  7. background-size: cover;
  8. background-position: center;
  9. `;
  10. document.getElementById('maskInner').style.cssText += `
  11. background: url(${newImg});
  12. background-size: cover;
  13. background-position: center;
  14. `;
  15. };

到这里,全部功能都实现完毕了,大家赶快制作一张自己喜欢的 史诗皮肤 或 奥德赛小游戏 的启动页面吧。

源码地址:https://github.com/dragonir/paint-heat-map

总结

本文包含的新知识点主要包括:

  • mask-image 遮罩元素
  • feTurbulence 和 feDisplacementMap svg滤镜
  • filter 属性
  • Canvas 绘制方法
  • TimelineMax 动画
  • input[type=file] 本地图片资源加载

到此这篇关于利用JavaScript实现静态图片局部流动效果的文章就介绍到这了,更多相关JavaScript图片局部流动内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号