波浪形动画效果,波浪动画很常见,但这个波浪组件绝对不常见

前言

最近项目优化的时候发现了一个巨大的累赘:一个78kbgif动态图:

这个动图是干嘛用的呢?就是在请求一些大面积数据等情况时显示一个我们网站的镂空Logo,然后会出现一些金黄色的液体像水漫金山一样填满这个Logo。由于我没法给大家展示我们公司的Logo,所以打了码。不过这样虽然看不清Logo了,但同时也会导致看不清到底是一种什么样的动画,所以我换了个大家耳熟能详的Logo,然后用组件库包装了一下给大家看看到底是一种什么样的效果:

不对,咱们是前端,干嘛要用这种庸俗的标呢?咱们应该搞点前端的标:

其实就是这样的一个波浪加载动画,完全可以用CSS写出来嘛!何必用一个78K之巨的动态图呢?想想一个Vue.js刚多大?而且这种波浪效果我记得好多文章都有写过,我还收藏过呢!赶紧点开收藏夹找找原来收藏过的文章:

这些波浪看起来和我们Logo的那个波浪好像不太一样啊,那这些浪都是怎么实现出来的呢:

看完这张图你应该就能明白了吧,利用的其实就是遮盖的原理,遮盖住原本平静的水面,看起来不就像是有浪了一样吗?如果你看不明白遮盖在上面的那堆看起来像是水蒸汽一样的雾状东西是怎么实现的话,看看下面这张图就能明白了: 其实就是个div加点圆角,就跟小米那个花两百万改了个圆角的Logo一样,然后再让它转起来,接下来把遮盖物的颜色换掉,最后再用overflow: hidden;隐藏掉圆圈外面的那部分就行啦: 是不是很巧妙呢?不过非常可惜的一点是这种方式有个缺陷:那就是它的背景必须得是纯色背景。我们网站的一些页面是有背景图的,想想看这样是不是就露馅儿了,因为那个遮盖物在不规则的背景下一眼就能被看出来。所以我们需要的是真·镂空,一顿搜寻过后终于找到了我想要的组件库:fancy-components,翻译过来就是花式组件库。光听名字也能猜得到:这不是像Element UIAnt Design那类组件库一样,而是能够为我们提供一些较为复杂样式的组件库。

花式组件库

首先这是一款基于web components的组件库,无论你是vuereactangular还是svelte等一些其他框架,它都能良好的与这些框架共存,因为这就是JS原生的组件化技术。没有哪个框架说自己不支持原生JS的某一部分API的,这种技术有人看好也有人唱衰,看好的人觉得:

除了fancy-components,我还经常用的一个web components组件是css doodle,翻译过来是CSS涂鸦的意思。它不能算是一个组件库,因为它只有一个组件,但是可以通过这个组件画出万花筒一样的样式来,也是超炫的一个web components组件:

2021-08-20 22-05-56.2021-08-20 22_06_44.gif

感兴趣的同学可以去B站搜索前端学不动这位up主,看看上面那个效果是怎么实现出来的。

现如今已经有很多组件库都采用了web components作为底层技术,已经可以看到这种趋势了。只不过web components在国内很少被人谈起,所以那些在国外还不错的一些组件库也就没能传到大家的耳朵里。

就连新发布的Vue3.2也出了一个defineCustomElement方法,就是为了专门用来生成自定义元素(web components的其中一项技术)的。目前已经有趋势表明这门技术未来将会大展身手,以前它没能火起来的主要原因就是兼容性问题。

不过既然有人看好那就肯定有人唱衰,唱衰的人觉得:

  • 几乎所有流行框架都拥有自己的一套组件化系统 何必大费周折的去用web components重构呢?
  • 这么多年了还没火起来 不是没给它时间 是它自己不争气啊!
  • 不兼容IE的技术不是好技术

为何说web components未来可能会取代框架这个观点有点扯淡呢?发布这个观点的作者认为jQuery的没落源于浏览器实现了document.querySelectordocument.querySelectorAll方法,这不就跟jQuery的主要方法$()有些相似么,那谁还用jq。所以web components也是同理:现代框架的主要卖点不就是组件化么?浏览器原生实现了组件化功能,所以现代框架未来也会像jq那样。(个人感觉纯属扯淡

不过毕竟咱们这篇文章不是讲web components的,只不过是咱们用到的组件库fancy-components是用web components来进行封装的,就简单的提一嘴web components。咱们只需要知道怎么用fancy-components就行:

在普通项目中

普通项目指的是那种直接在文件夹里建个HTML文件就开始写代码的那种项目,没有什么工程化、webpack、babel这些花里胡哨的东西。我们新建个HTML然后在``标签中写上type="module"就能使用模块化的语法来加载fancy-components啦:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>公众号:前端学不动</title>
  <style>
    * {
      padding: 0;
      margin: 0;
    }
    html, body {
      height: 100%;
      display: grid;
      place-items: center;
    }
    fc-wave-filter {
      --width: 100px;
    }

    /* :defined 选择器会选择到已定义的元素 */
    /* 但却没有 :undefined 这样的选择器 所以只能用 :not() 选择器来配合使用 */
    /* 这个选择器具体是干嘛用的 可以B站搜索:前端学不动 这位up主 */
    /* 找到他的《自定义元素基础篇》在第 17:25 的位置有讲 */
    :not(:defined) {
      display: none;
    }
  </style>
</head>
<body>
  
  <fc-wave-filter color="#047">
    
    <img src="data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgc3R5bGU9IndpZHRoOjFlbTtoZWlnaHQ6MWVtO3ZlcnRpY2FsLWFsaWduOm1pZGRsZSIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBmaWxsPSJjdXJyZW50Q29sb3IiIG92ZXJmbG93PSJoaWRkZW4iPjxwYXRoIGQ9Ik0yNjQuMTI5IDI2MS4yNjJjNS4wMjUtNS45NCA0LjI4NS0xNS4yNS0zLjAwOS0yMS40MS02LjQ3NS01LjQzNS0xNi4wODUtMy45MzgtMjAuMzU0IDEuMTk4bC0zOC4wNzcgNDUuMzIzIDIzLjY2MiAxOS44NjYgMzcuNzc4LTQ0Ljk3N3ptLTQzLjkzNy01MC4wMTljLTYuMTQ0LTUuMTA0LTE1LjU2NS01LjI3Ny0xOS43ODctLjMxNWwtMzYuMjAzIDQzLjE2NiAyMi40MDIgMTguNzk0IDM1Ljc5My00Mi42NjFjNC41MzctNS4zNTcgNC41MDYtMTMuMzQ0LTIuMjA1LTE4Ljk4NHptNTY2LjQxMyAxMzUuODE0bC0yMi40NDktMjcuOTE2IDQ0LjcyNS02NS4zNzgtLjI4My0uMzE1LTczLjIwOCAzMC4xMjEtMjIuNjg2LTI3Ljc1OCA3Ny40OTMtOTkuNzIyIDE5LjEyNSAyMy42My00OC41MjIgNjMuNTA1IDc0LjE4NS0zMS44NyAyMC41MTIgMjUuMzYzLTQ2LjU2OSA2NS45MyA3Mi4yLTM0LjI4IDE5LjEyNiAyMy41OTktMTEzLjY0OSA1NS4wOTF6TTUwNy4xNjQgODA3LjAzOGMtMTY1LjYwNSAwLTI5OS44NzUtMTM0LjI1NC0yOTkuODc1LTI5OS44NzQgMC0xNjUuNTczIDEzNC4yNy0yOTkuODQzIDI5OS44NzUtMjk5Ljg0MyAxNjUuNjIgMCAyOTkuODc0IDEzNC4yNTQgMjk5Ljg3NCAyOTkuODQzIDAgMTY1LjYyLTEzNC4yNTQgMjk5Ljg3NC0yOTkuODc0IDI5OS44NzR6TTEyNi45MjkgMjY0Ljk4bDYwLjI5LTcxLjgyMmMxMi41MDgtMTQuODg3IDI5Ljk2NC0yNi43ODIgNDYuNTUyLTEyLjg0IDUuMTA1IDQuMjcgNy4yOTQgNS42ODggMTMuNTAxIDE3LjA0NiAxLjQ2NSA0LjAwMiAyLjU4NCAxMS4yOTYtLjE4OSAxOC40NDggMTQuNDE1LTguOTY0IDI5Ljc5LTYuMDM0IDM5LjcxNiAzLjYyMyAxNS4yODEgMTQuODU2IDEzLjA2IDMxLjk5Ni0xLjQ2NSA0OS4zMjZsLTYzLjU4MyA3NS43NzYtOTQuODIyLTc5LjU1N3ptMzExLjYxLTIwMy4yNTZoMzcuMjI3bDMzLjYxOSA4OCAzMy42MTktODhoMzcuMTYzdjEyMy41NzNoLTI0LjczNHYtODYuM2wtMzIuNTQ3IDg2LjNoLTI3LjA1bC0zMi40ODQtODYuM3Y4Ni4zSDQzOC41NFY2MS43MjR6bTY4LjYyNS0yMy45OTRDMjQ3LjkxOCAzNy43MyAzNy43MyAyNDcuOTE4IDM3LjczIDUwNy4xNjRjMCAyNTkuMjc2IDIxMC4xODggNDY5LjQ4IDQ2OS40MzQgNDY5LjQ4IDI1OS4zMDggMCA0NjkuNDgtMjEwLjIwNCA0NjkuNDgtNDY5LjQ2NVM3NjYuNDcyIDM3LjcxNSA1MDcuMTY0IDM3LjcxNXptMCA3NDkuOTk0YzE1NC45ODYgMCAyODAuNjA3LTEyNS41OSAyODAuNjA3LTI4MC41Nkg1MDcuMTY0Vjc4Ny43NHptLTI4MC41NzYtMjgwLjU2aDI4MC41NzZWMjI2LjYxOWMtMTU0Ljk3MSAwLTI4MC41NzYgMTI1LjYwNS0yODAuNTc2IDI4MC41NDV6bTI4MC41NzYgNDg5LjYzYy0yNjkuOTU4IDAtNDg5LjY0Ni0yMTkuNjU3LTQ4OS42NDYtNDg5LjYzIDAtMjY5Ljk1OCAyMTkuNjg4LTQ4OS42MyA0ODkuNjQ2LTQ4OS42MyAyNzAuMDA1IDAgNDg5LjY0NSAyMTkuNjcyIDQ4OS42NDUgNDg5LjYzIDAgMjY5Ljk3My0yMTkuNjQgNDg5LjYzLTQ4OS42NDUgNDg5LjYzem0wLTk5Ni43OTRDMjI3LjUgMCAwIDIyNy41MDEgMCA1MDcuMTY0YzAgMjc5LjY2MiAyMjcuNTAxIDUwNy4xOTUgNTA3LjE2NCA1MDcuMTk1IDI3OS42OTMgMCA1MDcuMTc5LTIyNy41MzMgNTA3LjE3OS01MDcuMTk1QzEwMTQuMzQzIDIyNy41IDc4Ni44NTcgMCA1MDcuMTYzIDB6IiBmaWxsPSIjQjlCRUMzIi8+PC9zdmc+" />
  </fc-wave-filter>

  <script type="module">
    // 从 fancy-components 中引入 FcWaveFilter(波浪滤镜)
    import { FcWaveFilter } from 'https://unpkg.com/fancy-components'

    // new 就相当于全局注册了这个组件,相当于 Vue 的 Vue.component('fc-wave-filter', FcWaveFilter)
    new FcWaveFilter()
  </script>
</body>
</html>

所用图片: WX20210825-165010.png

fancy-components中有个叫FcWaveFilter的组件,翻译过来就是波浪滤镜,也很形象,用这个组件把<img>标签包裹一下就可以变成这样:

在 React 项目中

npm i fancy-components

先在主文件中引入你想要的组件,然后 new 一下:

/* eslint-disable no-new */
import React from 'react'
import ReactDom from 'react-dom'
...
// 从 fancy-components 中引入 FcWaveFilter(波浪滤镜)
import { FcWaveFilter } from 'fancy-components'

// new 就相当于全局注册了这个组件,相当于 Vue 的 Vue.component('fc-wave-filter', FcWaveFilter)
new FcWaveFilter()

然后在用的时候(xxx.jsx):

export default () => (
  <fc-wave-filter color="#047">
    <img src="data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgc3R5bGU9IndpZHRoOjFlbTtoZWlnaHQ6MWVtO3ZlcnRpY2FsLWFsaWduOm1pZGRsZSIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBmaWxsPSJjdXJyZW50Q29sb3IiIG92ZXJmbG93PSJoaWRkZW4iPjxwYXRoIGQ9Ik0yNjQuMTI5IDI2MS4yNjJjNS4wMjUtNS45NCA0LjI4NS0xNS4yNS0zLjAwOS0yMS40MS02LjQ3NS01LjQzNS0xNi4wODUtMy45MzgtMjAuMzU0IDEuMTk4bC0zOC4wNzcgNDUuMzIzIDIzLjY2MiAxOS44NjYgMzcuNzc4LTQ0Ljk3N3ptLTQzLjkzNy01MC4wMTljLTYuMTQ0LTUuMTA0LTE1LjU2NS01LjI3Ny0xOS43ODctLjMxNWwtMzYuMjAzIDQzLjE2NiAyMi40MDIgMTguNzk0IDM1Ljc5My00Mi42NjFjNC41MzctNS4zNTcgNC41MDYtMTMuMzQ0LTIuMjA1LTE4Ljk4NHptNTY2LjQxMyAxMzUuODE0bC0yMi40NDktMjcuOTE2IDQ0LjcyNS02NS4zNzgtLjI4My0uMzE1LTczLjIwOCAzMC4xMjEtMjIuNjg2LTI3Ljc1OCA3Ny40OTMtOTkuNzIyIDE5LjEyNSAyMy42My00OC41MjIgNjMuNTA1IDc0LjE4NS0zMS44NyAyMC41MTIgMjUuMzYzLTQ2LjU2OSA2NS45MyA3Mi4yLTM0LjI4IDE5LjEyNiAyMy41OTktMTEzLjY0OSA1NS4wOTF6TTUwNy4xNjQgODA3LjAzOGMtMTY1LjYwNSAwLTI5OS44NzUtMTM0LjI1NC0yOTkuODc1LTI5OS44NzQgMC0xNjUuNTczIDEzNC4yNy0yOTkuODQzIDI5OS44NzUtMjk5Ljg0MyAxNjUuNjIgMCAyOTkuODc0IDEzNC4yNTQgMjk5Ljg3NCAyOTkuODQzIDAgMTY1LjYyLTEzNC4yNTQgMjk5Ljg3NC0yOTkuODc0IDI5OS44NzR6TTEyNi45MjkgMjY0Ljk4bDYwLjI5LTcxLjgyMmMxMi41MDgtMTQuODg3IDI5Ljk2NC0yNi43ODIgNDYuNTUyLTEyLjg0IDUuMTA1IDQuMjcgNy4yOTQgNS42ODggMTMuNTAxIDE3LjA0NiAxLjQ2NSA0LjAwMiAyLjU4NCAxMS4yOTYtLjE4OSAxOC40NDggMTQuNDE1LTguOTY0IDI5Ljc5LTYuMDM0IDM5LjcxNiAzLjYyMyAxNS4yODEgMTQuODU2IDEzLjA2IDMxLjk5Ni0xLjQ2NSA0OS4zMjZsLTYzLjU4MyA3NS43NzYtOTQuODIyLTc5LjU1N3ptMzExLjYxLTIwMy4yNTZoMzcuMjI3bDMzLjYxOSA4OCAzMy42MTktODhoMzcuMTYzdjEyMy41NzNoLTI0LjczNHYtODYuM2wtMzIuNTQ3IDg2LjNoLTI3LjA1bC0zMi40ODQtODYuM3Y4Ni4zSDQzOC41NFY2MS43MjR6bTY4LjYyNS0yMy45OTRDMjQ3LjkxOCAzNy43MyAzNy43MyAyNDcuOTE4IDM3LjczIDUwNy4xNjRjMCAyNTkuMjc2IDIxMC4xODggNDY5LjQ4IDQ2OS40MzQgNDY5LjQ4IDI1OS4zMDggMCA0NjkuNDgtMjEwLjIwNCA0NjkuNDgtNDY5LjQ2NVM3NjYuNDcyIDM3LjcxNSA1MDcuMTY0IDM3LjcxNXptMCA3NDkuOTk0YzE1NC45ODYgMCAyODAuNjA3LTEyNS41OSAyODAuNjA3LTI4MC41Nkg1MDcuMTY0Vjc4Ny43NHptLTI4MC41NzYtMjgwLjU2aDI4MC41NzZWMjI2LjYxOWMtMTU0Ljk3MSAwLTI4MC41NzYgMTI1LjYwNS0yODAuNTc2IDI4MC41NDV6bTI4MC41NzYgNDg5LjYzYy0yNjkuOTU4IDAtNDg5LjY0Ni0yMTkuNjU3LTQ4OS42NDYtNDg5LjYzIDAtMjY5Ljk1OCAyMTkuNjg4LTQ4OS42MyA0ODkuNjQ2LTQ4OS42MyAyNzAuMDA1IDAgNDg5LjY0NSAyMTkuNjcyIDQ4OS42NDUgNDg5LjYzIDAgMjY5Ljk3My0yMTkuNjQgNDg5LjYzLTQ4OS42NDUgNDg5LjYzem0wLTk5Ni43OTRDMjI3LjUgMCAwIDIyNy41MDEgMCA1MDcuMTY0YzAgMjc5LjY2MiAyMjcuNTAxIDUwNy4xOTUgNTA3LjE2NCA1MDcuMTk1IDI3OS42OTMgMCA1MDcuMTc5LTIyNy41MzMgNTA3LjE3OS01MDcuMTk1QzEwMTQuMzQzIDIyNy41IDc4Ni44NTcgMCA1MDcuMTYzIDB6IiBmaWxsPSIjQjlCRUMzIi8+PC9zdmc+" />
  </fc-wave-filter>
)

注意千万不要写成驼峰形式的标签:<FcWaveFilter>

因为写组件写习惯了,想用组件的时候自然而然的就会想到这种写法。但实际上<fc-wave-filter>就和那些<div><a><p><ul><li>标签一样,属于HTML的原生标签啦!毕竟是用浏览器原生的组件化系统(web components)实现的,而不是采用了React的组件化系统,所以表现形式还是不一样的,不要搞混咯!而且也不需要像以前那样在用之前要引入一下:

// 不需要写成这样:
import { FcWaveFilter } from 'fancy-components'

export default () => (
  <fc-wave-filter color="#047">
    <img src="data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgc3R5bGU9IndpZHRoOjFlbTtoZWlnaHQ6MWVtO3ZlcnRpY2FsLWFsaWduOm1pZGRsZSIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBmaWxsPSJjdXJyZW50Q29sb3IiIG92ZXJmbG93PSJoaWRkZW4iPjxwYXRoIGQ9Ik0yNjQuMTI5IDI2MS4yNjJjNS4wMjUtNS45NCA0LjI4NS0xNS4yNS0zLjAwOS0yMS40MS02LjQ3NS01LjQzNS0xNi4wODUtMy45MzgtMjAuMzU0IDEuMTk4bC0zOC4wNzcgNDUuMzIzIDIzLjY2MiAxOS44NjYgMzcuNzc4LTQ0Ljk3N3ptLTQzLjkzNy01MC4wMTljLTYuMTQ0LTUuMTA0LTE1LjU2NS01LjI3Ny0xOS43ODctLjMxNWwtMzYuMjAzIDQzLjE2NiAyMi40MDIgMTguNzk0IDM1Ljc5My00Mi42NjFjNC41MzctNS4zNTcgNC41MDYtMTMuMzQ0LTIuMjA1LTE4Ljk4NHptNTY2LjQxMyAxMzUuODE0bC0yMi40NDktMjcuOTE2IDQ0LjcyNS02NS4zNzgtLjI4My0uMzE1LTczLjIwOCAzMC4xMjEtMjIuNjg2LTI3Ljc1OCA3Ny40OTMtOTkuNzIyIDE5LjEyNSAyMy42My00OC41MjIgNjMuNTA1IDc0LjE4NS0zMS44NyAyMC41MTIgMjUuMzYzLTQ2LjU2OSA2NS45MyA3Mi4yLTM0LjI4IDE5LjEyNiAyMy41OTktMTEzLjY0OSA1NS4wOTF6TTUwNy4xNjQgODA3LjAzOGMtMTY1LjYwNSAwLTI5OS44NzUtMTM0LjI1NC0yOTkuODc1LTI5OS44NzQgMC0xNjUuNTczIDEzNC4yNy0yOTkuODQzIDI5OS44NzUtMjk5Ljg0MyAxNjUuNjIgMCAyOTkuODc0IDEzNC4yNTQgMjk5Ljg3NCAyOTkuODQzIDAgMTY1LjYyLTEzNC4yNTQgMjk5Ljg3NC0yOTkuODc0IDI5OS44NzR6TTEyNi45MjkgMjY0Ljk4bDYwLjI5LTcxLjgyMmMxMi41MDgtMTQuODg3IDI5Ljk2NC0yNi43ODIgNDYuNTUyLTEyLjg0IDUuMTA1IDQuMjcgNy4yOTQgNS42ODggMTMuNTAxIDE3LjA0NiAxLjQ2NSA0LjAwMiAyLjU4NCAxMS4yOTYtLjE4OSAxOC40NDggMTQuNDE1LTguOTY0IDI5Ljc5LTYuMDM0IDM5LjcxNiAzLjYyMyAxNS4yODEgMTQuODU2IDEzLjA2IDMxLjk5Ni0xLjQ2NSA0OS4zMjZsLTYzLjU4MyA3NS43NzYtOTQuODIyLTc5LjU1N3ptMzExLjYxLTIwMy4yNTZoMzcuMjI3bDMzLjYxOSA4OCAzMy42MTktODhoMzcuMTYzdjEyMy41NzNoLTI0LjczNHYtODYuM2wtMzIuNTQ3IDg2LjNoLTI3LjA1bC0zMi40ODQtODYuM3Y4Ni4zSDQzOC41NFY2MS43MjR6bTY4LjYyNS0yMy45OTRDMjQ3LjkxOCAzNy43MyAzNy43MyAyNDcuOTE4IDM3LjczIDUwNy4xNjRjMCAyNTkuMjc2IDIxMC4xODggNDY5LjQ4IDQ2OS40MzQgNDY5LjQ4IDI1OS4zMDggMCA0NjkuNDgtMjEwLjIwNCA0NjkuNDgtNDY5LjQ2NVM3NjYuNDcyIDM3LjcxNSA1MDcuMTY0IDM3LjcxNXptMCA3NDkuOTk0YzE1NC45ODYgMCAyODAuNjA3LTEyNS41OSAyODAuNjA3LTI4MC41Nkg1MDcuMTY0Vjc4Ny43NHptLTI4MC41NzYtMjgwLjU2aDI4MC41NzZWMjI2LjYxOWMtMTU0Ljk3MSAwLTI4MC41NzYgMTI1LjYwNS0yODAuNTc2IDI4MC41NDV6bTI4MC41NzYgNDg5LjYzYy0yNjkuOTU4IDAtNDg5LjY0Ni0yMTkuNjU3LTQ4OS42NDYtNDg5LjYzIDAtMjY5Ljk1OCAyMTkuNjg4LTQ4OS42MyA0ODkuNjQ2LTQ4OS42MyAyNzAuMDA1IDAgNDg5LjY0NSAyMTkuNjcyIDQ4OS42NDUgNDg5LjYzIDAgMjY5Ljk3My0yMTkuNjQgNDg5LjYzLTQ4OS42NDUgNDg5LjYzem0wLTk5Ni43OTRDMjI3LjUgMCAwIDIyNy41MDEgMCA1MDcuMTY0YzAgMjc5LjY2MiAyMjcuNTAxIDUwNy4xOTUgNTA3LjE2NCA1MDcuMTk1IDI3OS42OTMgMCA1MDcuMTc5LTIyNy41MzMgNTA3LjE3OS01MDcuMTk1QzEwMTQuMzQzIDIyNy41IDc4Ni44NTcgMCA1MDcuMTYzIDB6IiBmaWxsPSIjQjlCRUMzIi8+PC9zdmc+" />
  </fc-wave-filter>
)

只要我们在主文件中引入并且new过就行:

import { FcWaveFilter } from 'fancy-components'

/* eslint-disable no-new */
new FcWaveFilter()

在 Vue2 中

React中很好的一点是组件和原生标签写法不一样,很容易区分:

import Ul form 'xxx'

export default () => (
  <Ul>
    <li/>
  </Ul>
)

大写的驼峰形式的标签就是组件,小写的标签就是HTML标签,这样相对容易区分。

Vue就不一样了,Vue组件既可以写成大写字母开头的驼峰形式,又可以写成小写字母开头的短横线形式。那么问题来了:当我们写了一个短横线连接的组件时,它怎么知道我们写的到底是组件还是标签呢?

它的做法是把所有HTML以及SVG标签全列举出来,只要你写的标签不在这个范围内,那么就默认你是组件了,如果你还没有在components里注册过,那么就会在控制台打印一个警告。所以我们要告诉Vue:对我这个既不在HTML标签范围内,又不在SVG标签范围内,同时还不是Vue组件的标签来说就不要给我报警告啦,我确定一定以及肯定这不是个错误!

npm i fancy-components

还是先来到主文件,引入fancy-components

import Vue from 'vue'
import App from './App'
import { FcWaveFilter } from 'fancy-components'

/* eslint-disable no-new */
new FcWaveFilter()

new Vue({
  render: h => h(App)
}).$mount('#app')

然后用Vue.config来配置需要忽略哪些标签:

import Vue from 'vue'
import App from './App'
import { FcWaveFilter } from 'fancy-components'

/* eslint-disable no-new */
new FcWaveFilter()

// 配置忽略以 fc- 作为开头的元素
Vue.config.ignoredElements = [/^fc-/]

new Vue({
  render: h => h(App)
}).$mount('#app')

在用的时候(xxx.vue):

<template>
  <fc-wave-filter color="#047">
    <img src="data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgc3R5bGU9IndpZHRoOjFlbTtoZWlnaHQ6MWVtO3ZlcnRpY2FsLWFsaWduOm1pZGRsZSIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBmaWxsPSJjdXJyZW50Q29sb3IiIG92ZXJmbG93PSJoaWRkZW4iPjxwYXRoIGQ9Ik0yNjQuMTI5IDI2MS4yNjJjNS4wMjUtNS45NCA0LjI4NS0xNS4yNS0zLjAwOS0yMS40MS02LjQ3NS01LjQzNS0xNi4wODUtMy45MzgtMjAuMzU0IDEuMTk4bC0zOC4wNzcgNDUuMzIzIDIzLjY2MiAxOS44NjYgMzcuNzc4LTQ0Ljk3N3ptLTQzLjkzNy01MC4wMTljLTYuMTQ0LTUuMTA0LTE1LjU2NS01LjI3Ny0xOS43ODctLjMxNWwtMzYuMjAzIDQzLjE2NiAyMi40MDIgMTguNzk0IDM1Ljc5My00Mi42NjFjNC41MzctNS4zNTcgNC41MDYtMTMuMzQ0LTIuMjA1LTE4Ljk4NHptNTY2LjQxMyAxMzUuODE0bC0yMi40NDktMjcuOTE2IDQ0LjcyNS02NS4zNzgtLjI4My0uMzE1LTczLjIwOCAzMC4xMjEtMjIuNjg2LTI3Ljc1OCA3Ny40OTMtOTkuNzIyIDE5LjEyNSAyMy42My00OC41MjIgNjMuNTA1IDc0LjE4NS0zMS44NyAyMC41MTIgMjUuMzYzLTQ2LjU2OSA2NS45MyA3Mi4yLTM0LjI4IDE5LjEyNiAyMy41OTktMTEzLjY0OSA1NS4wOTF6TTUwNy4xNjQgODA3LjAzOGMtMTY1LjYwNSAwLTI5OS44NzUtMTM0LjI1NC0yOTkuODc1LTI5OS44NzQgMC0xNjUuNTczIDEzNC4yNy0yOTkuODQzIDI5OS44NzUtMjk5Ljg0MyAxNjUuNjIgMCAyOTkuODc0IDEzNC4yNTQgMjk5Ljg3NCAyOTkuODQzIDAgMTY1LjYyLTEzNC4yNTQgMjk5Ljg3NC0yOTkuODc0IDI5OS44NzR6TTEyNi45MjkgMjY0Ljk4bDYwLjI5LTcxLjgyMmMxMi41MDgtMTQuODg3IDI5Ljk2NC0yNi43ODIgNDYuNTUyLTEyLjg0IDUuMTA1IDQuMjcgNy4yOTQgNS42ODggMTMuNTAxIDE3LjA0NiAxLjQ2NSA0LjAwMiAyLjU4NCAxMS4yOTYtLjE4OSAxOC40NDggMTQuNDE1LTguOTY0IDI5Ljc5LTYuMDM0IDM5LjcxNiAzLjYyMyAxNS4yODEgMTQuODU2IDEzLjA2IDMxLjk5Ni0xLjQ2NSA0OS4zMjZsLTYzLjU4MyA3NS43NzYtOTQuODIyLTc5LjU1N3ptMzExLjYxLTIwMy4yNTZoMzcuMjI3bDMzLjYxOSA4OCAzMy42MTktODhoMzcuMTYzdjEyMy41NzNoLTI0LjczNHYtODYuM2wtMzIuNTQ3IDg2LjNoLTI3LjA1bC0zMi40ODQtODYuM3Y4Ni4zSDQzOC41NFY2MS43MjR6bTY4LjYyNS0yMy45OTRDMjQ3LjkxOCAzNy43MyAzNy43MyAyNDcuOTE4IDM3LjczIDUwNy4xNjRjMCAyNTkuMjc2IDIxMC4xODggNDY5LjQ4IDQ2OS40MzQgNDY5LjQ4IDI1OS4zMDggMCA0NjkuNDgtMjEwLjIwNCA0NjkuNDgtNDY5LjQ2NVM3NjYuNDcyIDM3LjcxNSA1MDcuMTY0IDM3LjcxNXptMCA3NDkuOTk0YzE1NC45ODYgMCAyODAuNjA3LTEyNS41OSAyODAuNjA3LTI4MC41Nkg1MDcuMTY0Vjc4Ny43NHptLTI4MC41NzYtMjgwLjU2aDI4MC41NzZWMjI2LjYxOWMtMTU0Ljk3MSAwLTI4MC41NzYgMTI1LjYwNS0yODAuNTc2IDI4MC41NDV6bTI4MC41NzYgNDg5LjYzYy0yNjkuOTU4IDAtNDg5LjY0Ni0yMTkuNjU3LTQ4OS42NDYtNDg5LjYzIDAtMjY5Ljk1OCAyMTkuNjg4LTQ4OS42MyA0ODkuNjQ2LTQ4OS42MyAyNzAuMDA1IDAgNDg5LjY0NSAyMTkuNjcyIDQ4OS42NDUgNDg5LjYzIDAgMjY5Ljk3My0yMTkuNjQgNDg5LjYzLTQ4OS42NDUgNDg5LjYzem0wLTk5Ni43OTRDMjI3LjUgMCAwIDIyNy41MDEgMCA1MDcuMTY0YzAgMjc5LjY2MiAyMjcuNTAxIDUwNy4xOTUgNTA3LjE2NCA1MDcuMTk1IDI3OS42OTMgMCA1MDcuMTc5LTIyNy41MzMgNTA3LjE3OS01MDcuMTk1QzEwMTQuMzQzIDIyNy41IDc4Ni44NTcgMCA1MDcuMTYzIDB6IiBmaWxsPSIjQjlCRUMzIi8+PC9zdmc+" />
  </fc-wave-filter>


<script>
export default {
  components: {
    // 不需要在这里进行注册 就把 <fc-wave-filter> 当成一个浏览器原生标签就行
  }
}
</script>

在 vue-cli 的 Vue3 项目中

Vue3 在写法上出现了很大的变化,没有Vue了,取而代之的是app

import { createApp } from 'vue'
import App from './App'
import { FcWaveFilter } from 'fancy-components'

/* eslint-disable no-new */
new FcWaveFilter()

createApp(App).mount('#app')

那么是不是把原来的Vue换成app就行了呢:

// 以前
Vue.config.ignoredElements = [/^fc-/]

// 现在
app.config.ignoredElements = [/^fc-/]

很可惜答案是否定的,Vue3处理起来相对要稍微麻烦一点,我们需要在根目录下的vue.config.js中(如果没有就自己新建一个):

module.exports = {
    chainWebpack: config => {
        config.module
            .rule('vue')
            .use('vue-loader')
            .tap(options => {
                options.compilerOptions = {
                    ...(options.compilerOptions || {}),
                    isCustomElement: tag => tag.startsWith('fc-')
                }
                return options
            })
    }
}

这样就可以啦!

在 Vite 的 Vue3 项目中

npm i fancy-components

来到主文件引入fancy-componentsnew一下:

import { createApp } from 'vue'
import App from './App'
import { FcWaveFilter } from 'fancy-components'

/* eslint-disable no-new */
new FcWaveFilter()

createApp(App).mount('#app')

然后我们再来到vite.config.js中,把配置改成这样即可:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue({
    template: {
      compilerOptions: {
        isCustomElement: tag => tag.startsWith('fc-')
      }
    }
  })]
})

推荐的 Vite 模板

现在很多人都用上了Vite,但发没发现Vite不像vue-cli那样,能让我们自己选配一个比较完整的项目,用Vite创建出来的Vue3项目可以算得上是十分简陋了,vuexvue-router这些没有的咱就不提了,连eslintpostcss等一些常用的东西全都没有,全部需要自己配。

但大家也知道,哪里有需求哪里就有轮子,GitHub 上肯定有人为我们配好了,我这一顿找终于找到一个比较符合我需求的项目:https://github.com/GetKnowledgeByGitHub/vite

最喜欢的一点就是文档全是中文的,倒不是说看不懂英文,只是英文看着确实没有中文看着舒服。用法:

npx degit GetKnowledgeByGitHub/vite 自己的项目名

比方说我们想在桌面创建一个叫 my-fxxking-app 的项目

那么我们就打开命令行并来到 desktop 这个文件夹,输入:npx degit GetKnowledgeByGitHub/vite my-fxxking-app即可。接下来再来到my-fxxking-app这个文件夹内,npm install安装依赖就行。

当我安装完依赖后yarn dev一启动,开启页面让我直呼好家伙!不得不佩服这位作者的脑洞:

莫名的感😂 气洋洋的感觉。这个项目正是用了fancy-components这个组件库,就冲它这股中国风🇨🇳我也必须得给点个star

基础用法

假设我们已经npm i fancy-components安装过了,并且已经在主文件(main.js或index.js)注册过了:

import { FcWaveFilter } from 'fancy-components'

/* eslint-disable no-new */
new FcWaveFilter() // 相当于全局注册了组件 注:web components 没有局部注册组件这一说

而且如果是Vue项目也已经配置过了忽略fc-开头的web components了的话,那么我们只需要用<fc-wave-filter>来包裹住一张浅灰色的(最好是纯色)镂空图片:

<fc-wave-filter>
  <img src="xxx.png" />
</fc-wave-filter>

所用图片:

最终效果:

可以看到<fc-wave-filter>非常智能的为我们添加了波浪效果,但这个波浪并不是我们想要的,因为我们没有指定颜色,所以默认为黑色。黑色再配上这个花的图案… 像是什么葬礼网站的加载动画似的…

这个标大家应该认识吧?是华为的标,原本颜色应该是中国红才对,所以我们在<fc-wave-filter>里写上color属性:

<fc-wave-filter color="#dd1028">
  <img src="xxx.png" />
</fc-wave-filter>

这样看起来是不是就好多了呢?除了在<fc-wave-filter>标签里写color属性,我们还可以在CSS里写--color属性:

<template>
  <fc-wave-filter>
    <img src="xxx.png" />
  </fc-wave-filter>
</template>

<style scoped>
fc-wave-filter {
  --color: #dd1028;
}
</style>

效果也是一样的。不过有些人可能不太明白为什么要用--color而不是color属性,这是因为color属性是控制字体颜色的,它控制不了我们的填充颜色。而前面带--的属性叫自定义属性,是fancy-components内部为我们封装好要在哪用这个自定义属性的,自定义属性也叫CSS变量,如果不太清楚什么是CSS变量的话可以点击这个链接学习一下。

⚠️ 标签内的color属性比--color这个CSS变量的优先级要高,这两种写法最好不要混用。

这种波浪滤镜非常适合扁平化的标,什么是扁平化的标呢?我们先来看个反例:

这个标很帅吧?非常立体的这么一种感觉,在阳光明媚下伴随着阵阵反光,虽说在现实中很帅,但它却不太适合展示在平面上:

感觉左上角那个标在这种平面图上还是把它做成非黑即白的线条比较好:

但其实还是不好看,因为有很多线条是为了描述立体形状的棱角的。扁平化还是要简洁点好:

线条有些粗犷… 所以 NISSAN 最终并未采用这个标,而是专门设计了新 LOGO,实现了真正的扁平化:

这回再放海报上看起来是不是就顺眼多了:

扁平化的好处在做展览时也会呈现出来:

那我们用<fc-wave-filter>来包裹一下这个标:

可以看到波浪的效果真的不咋明显,效果一般,反而不如它以前那个粗犷的标:

这证明什么?证明细线条的标不适合用波浪特效?这还真不一定,我们来找一个同样是细线条的Logo试下:

所以说这证明了线条简单还细的Logo在用波浪特效时效果一般,线条虽细但足够复杂的话效果也还是不错的。

那假如NISSAN那个标就是想要Logo加载特效怎么办呢?我们还是有别的办法的,那就是修改振幅

振幅其实就是表示你这个浪有多大的,振幅越大就越浪。振幅的英文是amplitude,所以我们可以在<fc-wave-filter>上加一个amplitude属性,amplitude="1"就相当于振幅是默认振幅的1倍,任何数乘以1都等于它自己,所以我们如果想要振幅大一点的话就可以给一个amplitude="1.5"表示振幅是原振幅的1.5倍。那么同理,任何数乘以0不都等于0么。我们给一个amplitude="0"不就表示振幅为0,不就浪不起来了么:

<fc-wave-filter color="#dd1028" amplitude="0">
  <img src="xxx.png" />
</fc-wave-filter>

既然都没有浪了,那么这个速度就显得有些慢了。动画从空一直到填满默认是3秒,如果想修改这个值的话就要用到dur这个属性了,就是duration(持续时间)的简写,我们让它2秒就填充完:

<fc-wave-filter color="#dd1028" amplitude="0" dur="2">
  <img src="xxx.png" />
</fc-wave-filter>

这里真的用了很多NISSAN的标啊 声明一下:我只是觉得这个标比较有代表性 NISSAN并没有给我广告费 官方人员看到后请速速给我打钱😆

我们在日常生活中有些人总是有意无意的露出各种品牌Logo来:比方说有人发朋友圈总爱拍上带有星巴克Logo的咖啡,这就证明有些人是故意想多展示一下某些标的对吧?那看看我们目前的效果:

填满她之后立马就清空掉然后再重新填满对吧?这怎么能行呢?我们在填满她之后还想要多待一会,展示一下她优美的面容呢!这就需要用到delay属性了,我们需要用delay="1"来让填充在她体内的液体多持续一秒:

<fc-wave-filter color="#006241" delay="1">
  <img src="xxx.png" />
</fc-wave-filter>

delayduramplitude这种数字属性都是支持小数的,比如delay="1"这种整数就显得太过装B刻意了,一般我们给个0.3秒就已经足够展示我们的logo了。超过这个数效果也不见得会有多好,刚刚的效果你们也都看到了…

不过这个组件也不是完美无缺的,有的标被它包裹之后效果就很不错,但有的标就不适合波浪特效。

发没发现我们刚刚展示的那些标基本上都有一些共同的特点?甭管是圆形的标方形的标还是三角形的标,他们的长和宽都差不多,也就是说他们是接近正方形的。要知道有些标它就特别长或者特别宽,特别宽的原因通常都是在原本偏向于正方形Logo的右侧又加入了一堆字符:

这种标也很常见吧!左侧一个图形Logo + 右侧一个文字Logo,这种标用波浪滤镜的效果是这样的:

可以看到由于这个标过宽导致了浪也被拉平了,那我们来修改一下振幅,让它比原来浪两倍:

<fc-wave-filter color="#006241" amplitude="2">
  <img src="xxx.png" />
</fc-wave-filter>

效果好像也还行,没我想的那么差,不过还是方正一点的标用这个波浪滤镜会更好看:

这个Windows(窗户)的标再加上蓝色的波浪,看起来真的就像是窗外水漫金山了一样。

高阶用法

大家发没发现我们用的图都是那种浅灰色的纯色图,这样再用鲜艳一点的颜色去填充时才好看,那假如我们不用纯色图,我们就用色彩鲜艳的彩色图会怎么样呢:

结果:

可以看到效果确实不太好,因为这个波浪是按照图片的镂空与否来进行填充的,但像这种图片在没镂空的地方也是有许多细节的,比如:头发、巫女帽、金箍棒、裙子等… 但是波浪在填充时把这些细节全部都给覆盖了,当填满她之后我们看到的就只是个剪影、只是个轮廓。虽然光从轮廓就能看出来这位小魔女的傲人身材,但有没有办法能够保留住没镂空那部分的细节呢?答案当然是肯定的,我们现在的填充模式默认是依据透明度来填充的,透明的那部分不填充,不透明的那部分就填充,这样就会造成非黑即白的填充模式,这种模式自然适合非黑即白的标:

但色彩丰富的的图片就不行了,此时我们就要用到另外一种模式:亮度模式。就拿小魔女这张图举例:

我们人类都是视觉动物,可以辨别丰富的色彩,那色盲咋办呢?他们分辨不出来色彩,难道他们眼中的小魔女是这样的:

答案必然是否定的,他们眼中的世界肯定不是只能看个剪影看个轮廓而已,他们也是可以分辨出来一些细节的,只不过他们分辨的方式是亮度:小魔女露出的那部分肌肤是亮色、手里拿的金箍棒以及跟金箍棒颜色相近的那些橘色就是稍微暗一点的亮色、头发和裙子是暗色、衣服和帽子是非常暗的颜色,这就是亮度模式。

模式的英文是mode、亮度的英文是luminance,那么mode="luminance"就是亮度模式啦:

<fc-wave-filter color="#047" mode="luminance">
  <img src="xxx.png" />
</fc-wave-filter>

亮度模式填充的颜色就是我们用color属性所指定的颜色,只不过会根据不同的亮度产生不同的透明度:

虽说这样做填充完毕后倒是能看出来细节了:

但是感觉效果也并没有没好到哪去,如果我们填充的颜色是图片的色彩该多好,就像这样:

但是color这个属性只能给纯色啊,这种复杂色彩要怎么写呢?我们还是要用到填充模式mode属性,mode一共可以给四个值,分别是:alpha(透明度模式)、luminance(亮度模式)、img(图片模式)、slideshow(幻灯片模式)。

<fc-wave-filter mode="img">
  <img src="xxx.png" />
</fc-wave-filter>

给了mode="img"就不用再指定color属性了,因为已经不是靠颜色去填充了,靠的是图片:

为什么跟以前的这种样子不太一样了呢:

以前是因为我们需要用传进来的那张图片去充当背景,然后再生成一个纯色的波浪动画,最后再把他俩叠加在一起,其实就是相当于:

但现在我们把mode设置成img,如果还按照以往的那种方式岂不是就变成了:

由于有一个一模一样的背景存在,而且填充还填充的是一模一样的色彩和形状,所以最终的效果就是啥也看不出来:

也就是说当mode="img"时是不会将传进来的图片作为背景的,只会把传进来的图片作为填充。要是我们还希望有一个背景怎么办呢?好办!我们传进去两张图片就可以了:

我们先把小魔女的轮廓给拽出来,单独生成一张图片:

然后把完整版小魔女当作第一个子元素传进去,而小魔女的线条轮廓图则当作第二个子元素传进去:

<fc-wave-filter mode="img">

  
  <img src="xxx.png" />

  
  <img src="xxx.png" />

</fc-wave-filter>

最终效果:

怎么样?是不是超帅?原理其实也非常简单,就是这样:

其实还可以尝试很多不同的效果,就看你的创意和素材跟不跟得上了:

但要注意一下几个要点:

  • 传进来的两张图大小必须一致
  • 两张图必须相关
  • 位置一定要对齐
  • 顺序不要搞错

如果我们传的图片既大小不一又毫不相干那就没什么意义了:

幻灯片模式

我们在好多影视或动漫作品里都见过这么一种场景:主角原本是个性格温和的老实人,甚至平时被欺负了也只是笑笑就过去了,但突然发生了某些事情:也许是被人互换了身体、也许是受到了什么沉重的打击、抑或是出于某些原因导致的性格巨变…

此时原本性格温和的主角开始变"坏"了,大家惊奇的发现变坏后的主角反而更帅了,比如被毒液侵染后的蜘蛛侠:

不同的面部表情让整张脸看起来都跟以前不一样了:

再比如被坏人互换身体后的卡卡罗特:

这种就非常适合幻灯片模式了,本来我是想找同一个人的相同轮廓的不同面部表情的素材,可惜找了好久都没有找到,不过我找到了这三张图:

goku.png

freeza.png

piccolo.png

幻灯片的英文是slideshow,我们来用幻灯片模式mode="slideshow"来看看效果:

<fc-wave-filter mode="slideshow">
  <img src="孙悟空(卡卡罗特).png" />
  <img src="弗利萨.png" />
  <img src="比克(短笛).png" />
</fc-wave-filter>

效果也不错,可以想象一下,如果我们能找到同一人物的相同轮廓的不同面部表情的素材,比如一张憨憨版卡卡罗特、一张超级赛亚人版卡卡罗特、再来一张被坏人换了身体的卡卡罗特,想想这样的效果该会有多棒!

不过目前的效果是换一张脸之后立刻就去换下一张脸了,我们希望能够像轮播图那样切换到一张图片后停留一会展示一下,此时该用什么属性?delay

<fc-wave-filter mode="slideshow" delay="2">
  <img src="弗利萨.png" />
  <img src="比克(短笛).png" />
</fc-wave-filter>

由于贴上来的动态图是无限循环的原因大家可能感受不到有什么不妥,我们一帧帧来看:

第一张图片一上来就直接被波浪特效给切过去了,这里其实我们是希望第一张图片一上来也是要展示两秒再切换的,红框里那部分图片才是正确的,它会一直展示两秒,而后再切回第一张的时候也会展示两秒,只有刚开场的时候才会不展示就直接切换,这是bug吗?还真不是,delay主要不是运用在这种场景的,而是运用在之前我们说的想多展示一会logo的那种场景:

虽说我们确实想多展示一会logo,但如果一上来就延迟两秒却没有任何填充的话,很可能会觉得这是卡了或者觉得这是个静态图片。因为这种动画有很多都是应用在了加载动画的场景下,只要不是数据量特别大或者网特别卡的情况下,加载时间通常都不会超过半秒钟,而在这半秒一秒的时间里这个logo一直都是不动的那效果可就不太好了。所以delay只会在动画结束时增加延迟效果。

可是现在我们不是要做加载动画,而是要做幻灯片动画怎么办呢?答案就是interval属性,它的用法和delay一模一样,都是传入一个数字,这个数字是几就会延迟几秒:

<fc-wave-filter mode="slideshow" interval="2">
  <img src="弗利萨.png" />
  <img src="比克(短笛).png" />
</fc-wave-filter>

只不过不一样的地方是interval在动画一开始时就会进行延迟:

这就非常适合我们的幻灯片效果啦!只需要记住:填充动画想要延迟就用delay属性、幻灯片动画要延迟就用inerval属性就行啦。

⚠️ 这俩属性最好别混着用 因为他俩同时指定的话会按照delay来生效

原理分析

相信大家一定跟我一样对fancy-components到底怎么实现的这一效果感到十分的好奇,我们不光要会用,更要了解其实现原理,打开控制台一顿鼓捣大概明白了是怎么回事,首先要这样:

然后再把露在外面的那部分隐藏起来:

这样看起来不就是波浪了吗?但我们想要的并不是一个正方形的波浪,那我们就来换个形状看看:

再把背景颜色再加深一些:

最后去掉露在外面的那部分波浪:

然后我们再配上一个浅灰色的背景:

这不就大功告成了么:

那我们是怎么实现奇形怪状的呢?答案就是clip-pathmask属性,假如我们有这么一张图片:

我们想让这个图片变个形状,就变成桃心❤️状吧!那么我们先需要准备一张❤️心形图片:

注意这张图片需要是镂空的png格式,就是心的那部分不透明,其他部分都透明,然后这样写代码:

<template>
  <img src="girl.gif" />
</template>

<style scoped>
img {
  -webkit-mask: url(xxx/heart.png)
}
</style>

为什么会出现一个半桃心❤️呢?这是因为mask是一个类似于background的属性,默认会平铺,如果不想平铺的话:

<template>
  <img src="girl.png" />
</template>

<style scoped>
img {
  -webkit-mask: url(xxx/heart.png);
  -webkit-mask-repeat: no-repeat;
}
</style>

同理,我们把小姐姐动图换成波浪动画不就变成了:

这就说明了<fc-wave-filter>这个组件把我们传进去的图片当成了背景图,然后再用这个背景图当作波浪动画的mask遮罩来让波浪变成我们传进来那张图的形状,最后再把我们传进去的图片以及图片形状的波浪叠在一起就变成了:

不得不说这个思路还真的是妙啊!不过<fc-wave-filter>用的倒不是CSSmask属性,而是SVG<mask>标签,因为他还要实现图片模式以及幻灯片模式等各种复杂功能,所以必须要用到更为底层的SVG,足以看出这位作者深厚的SVG功底,让我内心当中的敬意油然而生,去了他的GitHub给他点了个赞,希望他不要因为没人关注他的项目而感到心灰意冷从而不再推陈出新。

自己封装

其实我们普通人也是可以实现出来这个效果的,假设大家都不懂SVG,画不出来那段波浪,那我们就用笨方法嘛:

接下来隐藏掉露在外面的那部分:

波浪的感觉就已经出来了,虽然没有<fc-wave-filter>的浪那么逼真吧,毕竟咱们这个是低配版的,接下来再用mask属性给它换个什么别的奇形怪状:

这个浪确实不如<fc-wave-filter>的好看,感觉有点假… 算了,不瞎折腾了,还是用人家的吧:

这回看着就顺眼多了。

基础用法总结

高阶用法可能并没有基础用法那么常用,所以咱们重点来回顾一下基础用法即可,首先我们先要在<fc-wave-filter>传入一张图片:

<fc-wave-filter>
  <img src="xxx.png" />
</fc-wave-filter>

这张图片最好是一张浅灰色的纯色镂空图:

组件的宽高就是我们传进来那张图的宽高,如果想要修改组件宽高的话,最好是用CSS--width--height属性:

<template>
  <fc-wave-filter>
    <img src="xxx.png" />
  </fc-wave-filter>
</template>

<style scoped>
fc-wave-filter {
  --width: 120px;
  --height: 100px;
}
</style>

--width--height的好处就是:如果你仅指定了一个宽或者仅指定了一个高,那么这个组件将会保持住图片的比例,但如果用的是widthheight,仅指定一个的话另一个的比例就不一定对了。

然后还可以用--color来指定填充颜色:

<template>
  <fc-wave-filter>
    <img src="xxx.png" />
  </fc-wave-filter>
</template>

<style scoped>
fc-wave-filter {
  --width: 120px;
  --color: #987129;
}
</style>

除了在CSS中写--color,还可以在组件的标签中写color属性,效果也是一样的:

<fc-wave-filter color="#987129">
  <img src="xxx.png" />
</fc-wave-filter>

⚠️ 标签内的color属性比--color这个CSS变量的优先级要高,这两种写法最好不要混用。

标签内一共有6个属性,它们分别是:colorduramplitudedelaymodeinterval

<fc-wave-filter color="xxx" dur="xxx" amplitude="xxx" delay="xxx" mode="xxx" interval="xxx">
  <img src="xxx.png" />
</fc-wave-filter>

除了在标签里写这些属性之外,我们还可以通过 dom.xxx 的方式,就跟以前操作 DOM 的形式一样:

<template>
  <fc-wave-filter ref="dom">
    <img src="xxx.png" />
  </fc-wave-filter>
</template>

<script>
export default {
  mounted () {
    const { dom } = this.$refs
    
    dom.id = 'xxx'
    dom.className = 'xxx'
    
    dom.color = 'xxx'
    dom.mode = 'xxx'
    dom.dur = 123
    dom.amplitude = 456
    dom.delay = 789
    dom.interval = 666
  }
}
</script>

效果也是一样的。

但需要注意的是:谷歌内核的浏览器在运行过一遍波浪动画之后再动态改变colormodeduramplitudedelayinterval的值将会停止动画的运行,这是谷歌浏览器对SVGSMIL动画支持的一个bug火狐Safari就没有这个问题,目前也没有很好的办法解决。不过很少会有需求要求动态改变波浪的模式等属性的,都是提前在标签上设置好的。

如果真的有需要根据某种条件动态改变波浪的运行,比如一会用普通模式一会用图片模式,那我们可以写两个标签然后通过v-if去切换嘛:

<template>
  <fc-wave-filter :mode="mode">
   <img src="xxx.png" />
  </fc-wave-filter>
</template>

<script>
export default {
  data: () => ({ mode: 'img' }),
  mounted () {
    // 不要这样动态改变 color、mode、dur、amplitude、delay、interval 等属性的值
    setTimeout(() => this.mode = 'alpha', 3500)
  }
}
</script>

<template>
  <div>
    <fc-wave-filter v-if="xxx" mode="img">
      <img src="xxx.png" />
    </fc-wave-filter>
    
    <fc-wave-filter v-else mode="alpha">
      <img src="xxx.png" />
    </fc-wave-filter>
  </div>
</template>

我们还可以通过:not(:defined)这个选择器来选择到web components还没有与DOM建立连接时的状态。什么是与DOM相连,什么又是:defined选择器?这就属于web components的知识点了,本文篇幅已经很长了就不再细讲了,感兴趣的同学可以B站搜索:前端学不动这位up主,找到他的《自定义元素基础篇》在第17:25的位置有讲,我的web components知识也是在这上面了解到的。

API

属性 中文含义 可选值 默认值
color 填充颜色 正常的CSS颜色值 'black'
dur 动画时长 大于0的数字(单位为秒) 3
amplitude 波浪幅度 数字(原波浪幅的倍数) 1
delay 填充后持续的时间 大于0的数字(单位为秒) 0
mode 动画模式 'alpha'(透明度)、'luminance'(亮度)、'img'(图片)、'slideshow'(幻灯片) 'alpha'
interval 几秒一切换(适合幻灯片模式) 大于0的数字(单位为秒) 0

CSS属性 中文含义 可选值 默认值
--color 填充颜色 正常的CSS颜色值 black
--width 组件的宽度 正常的CSS宽度值 fit-content
--height 组件的高度 正常的CSS高度值

波浪形动画效果,波浪动画很常见,但这个波浪组件绝对不常见的相似文章

案例+图解带你一文读懂Canvas🔥🔥(2W+字)分析巧用 -webkit-box-reflect 倒影实现各类动效分析web代码编辑器有哪些,WEB代码编辑器哪家强分析css滚动条样式代码,实现一个渐变的滚动条分析vue踩坑记录,巨坑巨坑巨坑 记录一下以防下次遇到分析一些有趣的 JavaScript 和 CSS 库分析个人 echarts踩坑记录分析毛玻璃特效,backdrop-filter,让你的网站熠熠生”毛’分析浅谈前端的状态管理分析