使用JS前端技术实现静态图片局部流动效果


Posted in Javascript onAugust 05, 2022

声明:本文涉及图文和模型素材仅用于个人学习、研究和欣赏,请勿二次修改、非法传播、转载、出版、商用、及进行其他获利行为。

背景

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

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

效果

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

?‍? 在线体验:https://dragonir.github.io/paint-heat-map/

?‍? 在线体验:https://codepen.io/dragonir/full/qBoxQKW

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

使用JS前端技术实现静态图片局部流动效果

? 衣袖飘动 貂蝉:猫影幻舞

使用JS前端技术实现静态图片局部流动效果

? 湖光波动

使用JS前端技术实现静态图片局部流动效果

? 文字液化

使用JS前端技术实现静态图片局部流动效果

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

实现

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

使用JS前端技术实现静态图片局部流动效果

? 注意,还有一个隐形的功能,当你绘制完成时,可以点击 ? 鼠标右键,然后选择保存图片,保存的这张图片就是我们绘制流体动画路径的热点图,利用这张热点图,使用本文的 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

  • feTurbulence:滤镜利用 Perlin 噪声函数创建了一个图像,利用它可以实现人造纹理比如说云纹、大理石纹等模拟滤镜效果。
  • feDisplacementMap:映射置换滤镜,该滤镜用来自图像中从 in2 到空间的像素值置换图像从 in 到空间的像素值。即它可以改变元素和图形的像素位置,通过遍历原图形的所有像素点,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')
}

使用JS前端技术实现静态图片局部流动效果

? 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;

兼容性:

使用JS前端技术实现静态图片局部流动效果

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

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 中,就能给喜欢的图片添加局部波动效果了,下面这张图片就是本示例页面使用的波动的热点路径图。

使用JS前端技术实现静态图片局部流动效果

② 生成动画

为了生成实时更新的波动效果,本文使用了 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;
  `;
};

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

使用JS前端技术实现静态图片局部流动效果

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

总结

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

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

到此这篇关于使用JS前端技术实现静态图片局部流动效果的文章就介绍到这了,更多相关js静态图片局部流动内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
解析jQuery的三种bind/One/Live事件绑定使用方法
Dec 30 Javascript
JavaScript排序算法之希尔排序的2个实例
Apr 04 Javascript
动态加载jQuery的方法
Jun 16 Javascript
jquery无限级联下拉菜单简单实例演示
Nov 23 Javascript
JQuery实现定时刷新功能代码
May 09 jQuery
vue2项目使用sass的示例代码
Jun 28 Javascript
JavaScript实现的简单加密解密操作示例
Jun 01 Javascript
解决vue2.0 element-ui中el-upload的before-upload方法返回false时submit()不生效问题
Aug 24 Javascript
Javascript三种字符串连接方式及性能比较
May 28 Javascript
原生js实现的金山打字小游戏(实例代码详解)
Mar 16 Javascript
JavaScript事件循环及宏任务微任务原理解析
Sep 02 Javascript
vue 使用饿了么UI仿写teambition的筛选功能
Mar 01 Vue.js
JavaScript圣杯布局与双飞翼布局实现案例详解
Aug 05 #Javascript
react中useState使用:如何实现在当前表格直接更改数据
Aug 05 #Javascript
pnpm对npm及yarn降维打击详解
Aug 05 #Javascript
JS前端可视化canvas动画原理及其推导实现
Aug 05 #Javascript
JS前端使用canvas实现扩展物体类和事件派发
Aug 05 #Javascript
JS前端canvas交互实现拖拽旋转及缩放示例
Aug 05 #Javascript
canvas 中如何实现物体的框选
Aug 05 #Javascript
You might like
如何开始收听短波广播
2021/03/01 无线电
JS类定义原型方法的两种实现的区别评论很多
2007/09/12 Javascript
JavaScript DOM 学习第五章 表单简介
2010/02/19 Javascript
js 页面关闭前的出现提示的实现代码
2011/05/25 Javascript
我的NodeJs学习小结(一)
2014/07/06 NodeJs
JS基础随笔(菜鸟必看篇)
2016/07/13 Javascript
JavaScript面试题大全(推荐)
2016/09/22 Javascript
js当前页面登录注册框,固定div,底层阴影的实例代码
2016/10/04 Javascript
Vue.js创建Calendar日历效果
2016/11/03 Javascript
将json转换成struts参数的方法
2016/11/08 Javascript
NodeJS遍历文件生产文件列表功能示例
2017/01/22 NodeJs
详解Angular 4.x NgTemplateOutlet
2017/05/24 Javascript
vue绑定的点击事件阻止冒泡的实例
2018/02/08 Javascript
使用sessionStorage解决vuex在页面刷新后数据被清除的问题
2018/04/13 Javascript
jquery获取元素到屏幕四周可视距离的方法
2018/09/05 jQuery
详解vue 路由跳转四种方式 (带参数)
2019/04/28 Javascript
Node.js API详解之 tty功能与用法实例分析
2020/04/27 Javascript
.netcore+vue 实现压缩文件下载功能
2020/09/24 Javascript
[40:29]2018DOTA2亚洲邀请赛 4.7总决赛 LGD vs Mineski 第一场
2018/04/10 DOTA
Python整型运算之布尔型、标准整型、长整型操作示例
2017/07/21 Python
python每天定时运行某程序代码
2019/08/16 Python
HTML5中div、article、section的区别及使用介绍
2013/08/14 HTML / CSS
德国婴儿推车和儿童安全座椅商店:BABYSHOP
2016/09/01 全球购物
东南亚旅游平台:The Trip Guru
2018/01/01 全球购物
美国运动鞋和服装网上商店:YCMC
2018/09/15 全球购物
美国便宜的横幅和标志印刷在线:Best of Signs
2019/05/29 全球购物
接口可以包含哪些成员
2012/09/30 面试题
在DELPHI中调用存储过程和使用内嵌SQL哪种方式更好
2016/11/22 面试题
幼儿园长自我鉴定
2013/10/17 职场文书
道德演讲稿
2014/05/21 职场文书
媒矿安全生产承诺书
2014/05/23 职场文书
孝老爱亲模范事迹材料
2014/05/25 职场文书
租车协议书范本2014
2014/11/17 职场文书
2014年客房部工作总结
2014/11/22 职场文书
2014年青年教师工作总结
2014/12/17 职场文书
同学聚会通知书
2015/04/20 职场文书