在过往,我们想要实现一个图片的渐隐消失。最常见的莫过于整体透明度的变化,像是这样:

<div class="img"></div>

div {
width: 300px;
height: 300px;
background: url(image.jpg);
transition: .4s;
}
.img:hover {
opacity: 0;
}

但是,CSS 的功能如此强大的今天。我们可以利用 CSS 实现的渐隐效果已经不再是如此的简单。

想想看,下面这样一个效果,是 CSS 能够实现的么?

答案是肯定的!本文就将一步一步,从零开始,仅仅使用一个标签,实现上述的图片渐隐效果。

这里,有两个核心的点:

  1. 如何将一张图片切割的这么细,切割成这么多块?
  2. 基于上述 (1)的基础上,又该如何分别控制这些小块的独立隐藏和展示呢?

莫慌,让我们一步一步来解决他们。

强大的 Mask

首先,我们需要用到 Mask。

在 CSS 中,mask 属性允许使用者通过遮罩或者裁切特定区域的图片的方式来隐藏一个元素的部分或者全部可见区域。

语法

最基本,使用 mask 的方式是借助图片,类似这样:

{
/* Image values */
mask: url(mask.png); /* 使用位图来做遮罩 */
mask: url(masks.svg#star); /* 使用 SVG 图形中的形状来做遮罩 */
}

当然,使用图片的方式后文会再讲。借助图片的方式其实比较繁琐,因为我们首先还得准备相应的图片素材,除了图片,mask 还可以接受一个类似 background 的参数,也就是渐变。

类似如下使用方法:

{
mask: linear-gradient(#000, transparent) /* 使用渐变来做遮罩 */
}

那该具体怎么使用呢?一个非常简单的例子,上述我们创造了一个从黑色到透明渐变色,我们将它运用到实际中,代码类似这样:

下面这样一张图片,叠加上一个从透明到黑色的渐变,

{
background: url(image.png) ;
mask: linear-gradient(90deg, transparent, #fff);
}

应用了 mask 之后,就会变成这样:

这个 DEMO,可以先简单了解到 mask 的基本用法。

这里得到了使用 mask 最重要结论:图片与 mask 生成的渐变的 transparent 的重叠部分,将会变得透明。

值得注意的是,上面的渐变使用的是 linear-gradient(90deg, transparent, #fff),这里的 #fff 纯色部分其实换成任意颜色都可以,不影响效果。

CodePen Demo -- 使用 MASK 的基本使用

使用 mask 实现 hover 隐藏图片

了解了 mask 的简单用法后,我们来看这样一个非常简单的例子,我们改造下上述的第一个 DEMO。

<div class="img"></div>
div {
width: 300px;
height: 300px;
background: url(image.jpg);
}
.img:hover {
mask: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0));
}

是的,利用 Mask,我们同样也可以得到近似的消失效果:

如果对于 Mask 你还不了解,你需要首先看看这篇:奇妙的 CSS MASK

当然,对于现在这个效果,有个很大的缺陷,那就是缺少了动画。图片是瞬间消失的。所以,我们还需要给上述的借助 mask 实现的图片消失效果添加上动画。

而这,就需要用上 CSS @property 了。

强大的 CSS @property

CSS @property,大家应该不那么陌生了。

@property CSS at-rule 是 CSS Houdini API 的一部分, 它允许开发者显式地定义他们的 CSS 自定义属性,允许进行属性类型检查、设定默认值以及定义该自定义属性是否可以被继承。

如果你对 CSS @property 还有所疑惑,建议你先快速读一读这篇文章 -- CSS @property,让不可能变可能

回到我们的正题,如果我们想给上述使用 Mask 的代码,添加上动画,我们期望代码大概是这样:

div {
width: 300px;
height: 300px;
background: url(image.jpg);
mask: linear-gradient(rgba(0, 0, 0, 1), rgba(0, 0, 0, 1));
}
.img:hover {
mask: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0));
}

这里,mask 的是从 mask: linear-gradient(rgba(0, 0, 0, 1), rgba(0, 0, 0, 1))mask: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0)) 变化的。

但是实际上,这样并不会产生任何的动画效果。

原因在于,我们 Mask 属性本身是不支持过渡动画的!

但是,利用上 CSS @property,整个效果就不一样了。借助,CSS @property,我们改造一下代码:

@property --m-0 {
syntax: "<number>";
initial-value: 1;
inherits: false;
}
div {
width: 300px;
height: 300px;
background: url(image.jpg);
mask: linear-gradient(90deg, rgba(0, 0, 0, var(--m-0)), rgba(0, 0, 0, var(--m-0)));
transition: --m-0 0.5s;
}
div:hover {
--m-0: 0;
}

我们利用 CSS @property 定义了一个名为 --m-0 的变量,然后,我们将整个动画过渡效果赋予了这个变量,而不是整个 mask。

利用这个小技巧,我们就可以成功的实现基于 mask 属性的动画效果:

借助多重 mask 分割图片

到了这一步,后面的步骤其实就很明朗了。

由于 mask 拥有和 background 一样的特性。因此,mask 是可以有多重 mask 的。也就是说,我们可以设置多个不同的 mask 效果给同一个元素。

什么意思呢?上面的效果只有一重 mask,我们稍微添加一些 mask 代码,让它变成 2 重 mask:

@property --m-0 {
syntax: "<number>";
initial-value: 1;
inherits: false;
}
@property --m-1 {
syntax: "<number>";
initial-value: 1;
inherits: false;
}
div {
mask:
linear-gradient(90deg, rgba(0, 0, 0, var(--m-0)), rgba(0, 0, 0, var(--m-0))),
linear-gradient(90deg, rgba(0, 0, 0, var(--m-1)), rgba(0, 0, 0, var(--m-1)));
mask-size: 50% 100%;
mask-position: left, right;
mask-repeat: no-repeat;
transition:
--m-0 0.3s,
--m-1 0.25s 0.15s;
}
div:hover {
--m-0: 0;
--m-1: 0;
}

这样,我们的步骤大概是:

  1. 首先将 mask 一分为二,左右两边各一个
  2. 然后,设置了两个基于 CSS @property 的变量,--m-0--m-0
  3. 然后,给它们设置了不同的过渡时间和过渡延迟时间
  4. 在 hover 的一瞬间,再将这两个变量的值,都置为 0,也就是实现 linear-gradient(90deg, rgba(0, 0, 0, 1), rgba(0, 0, 0, 1))linear-gradient(90deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0)) 的变化,用于隐藏对应 mask 块
  5. 由于设置了不同的过渡时间和延迟时间,整体上看上去,整个动画就分成了两部分

看看效果:

继续切割为 4 重 mask

好,既然 2 重 mask 效果没问题,那么我们可以再进一步,将整个效果切割为 4 个 mask。代码还是如法炮制,这里我再贴上核心代码:

@property --m-0 {
syntax: "<number>";
initial-value: 1;
inherits: false;
}
@property --m-1 {
syntax: "<number>";
initial-value: 1;
inherits: false;
}
@property --m-2 {
syntax: "<number>";
initial-value: 1;
inherits: false;
}
@property --m-3 {
syntax: "<number>";
initial-value: 1;
inherits: false;
}
div {
mask:
linear-gradient(90deg, rgba(0, 0, 0, var(--m-0)), rgba(0, 0, 0, var(--m-0))),
linear-gradient(90deg, rgba(0, 0, 0, var(--m-1)), rgba(0, 0, 0, var(--m-1))),
linear-gradient(90deg, rgba(0, 0, 0, var(--m-2)), rgba(0, 0, 0, var(--m-2))),
linear-gradient(90deg, rgba(0, 0, 0, var(--m-3)), rgba(0, 0, 0, var(--m-3)));
mask-size: 50% 50%;
mask-repeat: no-repeat;
mask-position: left top, right top, left bottom, bottom right;
transition:
--m-0 0.3s,
--m-1 0.15s 0.1s,
--m-2 0.25s 0.21s,
--m-3 0.19s 0.15s;
}
div:hover {
--m-0: 0;
--m-1: 0;
--m-2: 0;
--m-3: 0;
}

这样,我们就可以得到 4 块分割图片的 mask 消失效果:

好,再依次类推,我们就可以得到分割为 9 块的,分割为 16 块的。由于代码太多,就简单看看效果:

CodePen Demo -- 基于 @property 和 mask 的图片渐隐消失术

基于 SCSS 简化代码

那么,如果我们要分割为 100 块呢?或者 400 块呢?还要手写这些代码吗?

当然不需要,由于上面的代码的规律非常的明显,我们可以借助预处理器很好的封装整个效果。从而快速的实现切割成任意规则块数的效果。

完整的代码如下:

$count: 400;
$sqrt: 20;
$per: 100% / $sqrt;
$width: 300px;
$perWid: 15; @for $i from 1 to ($count + 1) {
@property --m-#{$i} {
syntax: "<number>";
initial-value: 1;
inherits: false;
}
}
@function bgSet($n) {
$bg : radial-gradient(rgba(0, 0, 0, var(--m-1)), rgba(0, 0, 0, var(--m-1))); @for $i from 2 through $n {
$bg: $bg, radial-gradient(rgba(0, 0, 0, var(--m-#{$i})), rgba(0, 0, 0, var(--m-#{$i})));
} @return $bg;
}
@function positionSet($n) {
$bgPosition: (); @for $i from 0 through ($n) {
@for $j from 0 through ($n - 1) {
$bgPosition: $bgPosition, #{$i * $perWid}px #{$j * $perWid}px;
}
} @return $bgPosition;
}
@function transitionSet($n) {
$transition: --m-1 0.1s 0.1s; @for $i from 1 through $n {
$transition: $transition, --m-#{$i} #{random(500)}ms #{random(500)}ms;
} @return $transition;
}
div {
width: $width;
height: $width;
background: url(image.jpg);
mask: bgSet($count);
mask-size: $per $per;
mask-repeat: no-repeat;
mask-position: positionSet($sqrt);
transition: transitionSet($count);
}
div:hover {
@for $i from 1 through $count {
--m-#{$i}: 0;
}
}

这里,简单解释一下,以生成 400 块小块为例子:

  1. 最上面的 SCSS 变量定义中,

    • $count 是我们最终生成的块数
    • $sqrt 是每行以及每列会拥有的块数
    • $per 是每一块占整体图片元素的百分比值
    • $width 是整个图片的宽高值
    • $perWid 是每一块的宽高值
  2. 利用了最上面的一段循环函数,批量的生成 CSS @property 变量,从 --m-0--m-400
  3. @function bgSet($n) {} 是生成 400 块 mask 片段
  4. @function positionSet($n) 是生成 400 块 mask 的 mask-position,也就是生成 400 段不同定位,让 400 块 mask 刚好覆盖整个图片
  5. @function transitionSet($n) {} 是随机设置每个块的动画时间和延迟时间
  6. 代码最下面,还有一段循环函数,生成 400 个 CSS @property 变量的 hover 值,当 hover 的时候,全部变成 0

这样,我们就实现了 400 分块的渐隐效果。效果如下:

CodePen Demo -- 基于 @property 和 mask 的图片渐隐消失术

调整过渡变量,控制方向

当然,上面我们的对每一个小块的 transition 的过渡时间和过渡延迟时间的设置,都是随机的:

@function transitionSet($n) {
$transition: --m-1 0.1s 0.1s; @for $i from 1 through $n {
$transition: $transition, --m-#{$i} #{random(500)}ms #{random(500)}ms;
} @return $transition;
}

我们完全可以通过一定的控制,让过渡效果不那么随机,譬如有一定的方向感。

下面,我们通过让动画的延迟时间与 $i,也就是 mask 小块的 index 挂钩:

@function transitionSet($n) {
$transition: --m-1 0.1s 0.1s; @for $i from 1 through $n {
$transition: $transition, --m-#{$i} #{100 + random(500)}ms #{($i / 50) * random(100)}ms;
} @return $transition;
}

那么,整个动画的方向就是从左往右逐渐消失:

CodePen Demo -- 基于 @property 和 mask 的图片渐隐消失术 2

当然,有意思的是,这个效果,不仅仅能够运用在图片上,它其实可以作用在任何元素之上!

譬如,我们有的只是一段纯文本,同样适用这个效果:

CodePen Demo -- 基于 @property 和 mask 的文本渐隐消失术

总结

到这里,简单总结一下。本文,我们核心利用了 CSS @propery 和 mask,实现了一些原本看上去需要非常多 div 才能实现或者是需要借助 Canvas 才能实现的效果。同时,我们借助了 SCSS 预处理器,在寻找到规律后,极大的简化了 CSS 代码的书写量。

到今天,强大的 CSS 已经允许我们去做越来越多更有意思的动效,CSS @propery 和 mask 这两个属性在现代 CSS 发挥了非常重要的作用,非常建议大家认真掌握以下这两个属性。

最后

好了,本文到此结束,希望本文对你有所帮助

如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。

现代 CSS 之高阶图片渐隐消失术的更多相关文章

  1. Python笔记_第四篇_高阶编程_魔法(术)方法详解(重载的再详解)

    1. 魔法方法是什么? 魔法方法(Magic Method)是Python比较独特的应用,它可以给你的类增加特殊的方法,如果你的对象实现了(重载),这些方法中的某一个,就会被Python所调用.正如装 ...

  2. 高阶 CSS 技巧在复杂动效中的应用

    最近我在 CodePen 上看到了这样一个有意思的动画: 整个动画效果是在一个标签内,借助了 SVG PATH 实现.其核心在于对渐变(Gradient)的究极利用. 完整的代码你可以看看这里 -- ...

  3. CSS 渐变锯齿消失术

    在 CSS 中,渐变(Gradient)可谓是最为强大的一个属性之一. 但是,经常有同学在使用渐变的过程中会遇到渐变图形产生的锯齿问题. 何为渐变锯齿? 那么,什么是渐变图形产生的锯齿呢? 简单的一个 ...

  4. line-height行高与图片的隐匿文本间隙消除方法

    (标注 文章来源 http://www.zhangxinxu.com/wordpress/2015/08/css-deep-understand-vertical-align-and-line-hei ...

  5. nginx实现动态分离,解决css和js等图片加载问题

    改帖专门为使用nginx,通过nginx把请求转发到web服务器再返回客户端的时候,解决css和js和图片加载不出来的问题. 如果没安装nginx,请访问一下地址进行安装 http://www.cnb ...

  6. javascript设计模式学习之三—闭包和高阶函数

    一.闭包 闭包某种程度上就是函数的内部函数,可以引用外部函数的局部变量.当外部函数退出后,如果内部函数依旧能被访问到,那么内部函数所引用的外部函数的局部变量就也没有消失,该局部变量的生存周期就被延续. ...

  7. Javascript 闭包与高阶函数 ( 一 )

    上个月,淡丶无欲 让我写一期关于 闭包 的随笔,其实惭愧,我对闭包也是略知一二 ,不能给出一个很好的解释,担心自己讲不出个所以然来. 所以带着学习的目的来写一写,如有错误,忘不吝赐教 . 为什么要有闭 ...

  8. 关于用css实现文本和图片垂直水平居中

    关于用css实现文本和图片垂直水平居中   一直相信好记性不如烂笔头,最近遇到很多用到垂直居中的,整理一下以便日后查阅. 一.文本垂直水平居中 1.水平居中: 文字水平居中没什么好说的,用text-a ...

  9. 高阶自定义View --- 粒子变幻、隧道散列、组合文字

    高阶自定义View --- 粒子变幻.隧道散列.组合文字 作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:h ...

  10. js 高阶函数 闭包

    摘自  https://www.cnblogs.com/bobodeboke/p/5594647.html 建议结合另外一篇关于闭包的文章一起阅读:http://www.cnblogs.com/bob ...

随机推荐

  1. 使用Portainer管理其他主机的docker应用有两种方式

    官方文档地址:https://docs.portainer.io/v/ce-2.9/admin/environments/add/docker 第一种方式 是在其他主机的docker 启动中放开237 ...

  2. python 代码执行顺序

    Python代码在执行过程中,遵循下面的基本原则: 普通语句,直接执行: 碰到函数,将函数体载入内存,并不直接执行 碰到类,执行类内部的普通语句,但是类的方法只载入,不执行 碰到if.for等控制语句 ...

  3. Nexus OSS 3 搭建并配置使用 Docker & Git LFS 仓库

    转载自:https://cloud.tencent.com/developer/article/1010590 1.Nexus OSS 3 介绍 我们知道 Nexus 是一个强大的 Maven 仓库管 ...

  4. 智能工厂的ERP和MES之间的区别?

    无论在哪里,ERP(Enterprise Resource Planning,企业资源计划)和MES(Manufacturing Execution System,即制造执行系统)系统都不是同样的东西 ...

  5. ERP 软件为什么很贵?

    ERP软件的贵体现在两个方面,一个是软件系统贵,一个是部署实施贵,究其原因,就是ERP专业性太高.内部业务逻辑忒复杂,而面向形形色色的实体企业时个性化需求又加剧了它整体的复杂性,实施部署客制化无法避免 ...

  6. 微信支付更新功能,商户转账到零钱 php版 (2022-10-12更新)

    <?php class WechatMerchantTransfer { /** * @notes 商家转账到零钱 * @param $batch_no //提现订单号 * @param $le ...

  7. 2022网刃杯ics

    ​ 目录 easyiec Ncsubj 喜欢移动的黑客 xyp07 ICS6-LED_BOOM 根据大佬的wp后,自己做了一遍 这次学到很多东西 ICS easyiec tcp追踪流直接能看到 ​编辑 ...

  8. PHP cURL抓取网上图片

    cURL的底层是由一个命令行工具实现的,用于获取远程文件或传输文件,更多的情况是用来模拟get/post表单提交.也可以用户文件上传,爬取文件,支持FTP/FTPS,HTTP/HTTPS等协议,通俗来 ...

  9. 27.路由器Routers

    像一些reils这样的web框架提供自动生成urls的功能,但是Django没有 rest framework为django添加了这一功能,以一种简单.快速.一致的方式 routers必须配合view ...

  10. Vivado_8位流水灯

    Design 代码中的计数器设置是因为我的开发板的时钟是100MHZ的,也就是1秒完成了100_000_000次时钟信号,所以我设置计数器为100_000_000次. 也就是说,我让流水灯的变化周期为 ...