记录-有意思的气泡 Loading 效果

Posted 林恒

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记录-有意思的气泡 Loading 效果相关的知识,希望对你有一定的参考价值。

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

今日,群友提问,如何实现这么一个 Loading 效果:

这个确实有点意思,但是这是 CSS 能够完成的?

没错,这个效果中的核心气泡效果,其实借助 CSS 中的滤镜,能够比较轻松的实现,就是所需的元素可能多点。参考我们之前的:

圆弧的实现

首先,我们可能需要实现这样一段圆弧:

这里需要用到的技术是:

角向渐变 conic-gradient() + mask 以及两个伪元素。图片示意如下:

 核心代码如下图:

HTML:

<div class="g-container">
  <div class="g-circle"></div>
</div>

CSS:

:root 
    --headColor: hsl(130, 75%, 75%);
    --endColor: hsl(60, 75%, 40%);

.g-container 
    position: relative;
    background: #000;

.g-circle 
    position: relative;
    width: 300px;
    height: 300px;
    border-radius: 50%;
    background: conic-gradient(
        var(--headColor) 0, 
        var(--headColor) 10%,
        hsl(120, 75%, 70%), 
        hsl(110, 75%, 65%), 
        hsl(100, 75%, 60%),
        hsl(90, 75%, 55%), 
        hsl(80, 75%, 50%),
        hsl(70, 75%, 45%),
        var(--endColor) 30%,
        var(--endColor) 35%,
        transparent 35%
    );
    mask: radial-gradient(transparent, transparent 119px, #000 120px, #000 120px, #000 100%);
    
    &::before,
    &::after 
        content: "";
        position: absolute;
        inset: 0;
        width: 30px;
        height: 30px;
        background: var(--headColor);
        top: 0;
        left: 135px;
        border-radius: 50%;
    
    
    &::after 
        background: var(--endColor);
        left: unset;
        top: 214px;
        right: 26px;
    

这样,我们就得到了这样一个图形:

气泡的实现

接下来,我们来实现尾部气泡向外扩散的效果。

由于这里涉及了多个气泡的不同运动动画,多个标签元素肯定是少不了了。

因此,接下来我们要做的事情:

  1. 我们需要多一组元素,将其绝对定位到上述圆环的头部或者尾部
  2. 给每个子元素随机设置多个大小不一的圆,颜色保持一致
  3. 给每个子元素随机设置不同方向的,向外扩散的位移动画
  4. 给每个子元素随机设置负的 animation-delay,造成动画上的先后顺序,并以此形成整个无限循环的气泡扩散动画

这里,由于有许多小气泡的动画,这个数量,我设置成了 100。那肯定是不能一个一个手写它们的动画代码,需要借助 SASS/LESS 等预处理器的循环、随机等函数。

核心代码如下:

HTML:

<div class="g-container">
  <div class="g-circle"></div>
  <ul class="g-bubbles">
    <li class="g-bubble"></li>
    // ... 共 100 个 bubble 元素 
    <li class="g-bubble"></li>
  </ul>
</div>

CSS:

// 上面圆环的代码,保持一致,下面只补充气泡动画的代码
.g-bubbles 
    position: absolute;
    width: 30px;
    height: 30px;
    border-radius: 50px;
    top: 100px;
    left: 235px;
    background: var(--headColor);


.g-bubble 
    position: absolute;
    border-radius: 50%;
    background-color: inherit;


@for $i from 1 through 100  
    .g-bubble:nth-child(#$i) 
        --rotate: calc(#random(360) * 1deg);
        --dis: calc(#random(100) * 1px);
        --width: calc(3px + #random(25) * 1px);
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: var(--width);
        height: var(--width);
        animation: move #(random(1500) + 1500) / 1000s ease-in-out -#random(3000) / 1000s infinite;
    


@keyframes move 
    0% 
        transform: translate(-50%, -50%) rotate(0deg);
    
    75% 
        opacity: .9;
    
    100% 
        transform: rotateZ(var(--rotate)) translate(-50%, var(--dis));
        opacity: .4;
    

核心在于 @for $i from 1 through 100 这段 SASS 代码内部,我们实现了上面说的 (2)(3)(4) 的功能点!

这样,我们就得到了这样一个效果,在尾部有大量气泡动画,不断向外扩散的效果:

借助滤镜实现粘性气泡效果

OK,到这里整个效果基本就做完了。当然,也是剩下最后最重要的一步,需要让多个气泡之间产生一种粘性融合的效果。

这个技巧在此前非常多篇文章中,也频繁提及过,就是利用 filter: contrast() 滤镜与 filter: blur() 滤镜。

如果你还不了解这个技巧,可以戳我的这篇文章看看:你所不知道的 CSS 滤镜技巧与细节

简述下该技巧:

单独将两个滤镜拿出来,它们的作用分别是:

  1. filter: blur(): 给图像设置高斯模糊效果。
  2. filter: contrast(): 调整图像的对比度。

但是,当他们“合体”的时候,产生了奇妙的融合现象。

仔细看两圆相交的过程,在边与边接触的时候,会产生一种边界融合的效果,通过对比度滤镜把高斯模糊的模糊边缘给干掉,利用高斯模糊实现融合效果。

基于此,我们再简单改造下我们的 CSS 代码,所需要加的代码量非常少:

  1. 加上滤镜 blur() 和 contrast() ,形成融合粘性效果
  2. 加上整个圆环的旋转即可效果
  3. 加上滤镜 hue-rotate(),实现色彩的变换动画
.g-container 
    // ... 保持一致
    background: #000;
    filter: blur(3px) contrast(5);
    animation: rotate 4s infinite linear;

@keyframes rotate 
    100% 
        transform: rotate(360deg);
        filter: blur(3px) contrast(5) hue-rotate(360deg);
    

就这样,我们就大致还原了题图的效果:

完整的代码,你可以戳这里:CodePen Demo -- Pure CSS Loading Animation

修复违和感

当然,上面的效果,乍一看还行,仔细看,违和感很重。

原因在于,扩散出来的小球也跟着半圆环一起进行了旋转动画,看上去就有点奇怪。

正确的做法应该是,圆环尾部的气泡应该是原地发散消失的。

那么,怎么能够做到气泡效果,一直发生在圆环的尾部,同时消失的时候又不跟着整个圆环一起进行旋转呢?我们想要的最终效果,应该是这样:

这里,我们可以拆解一下。想象,如果去掉圆环的旋转,其实我们只需要实现这样一个效果即可:

整个动画的核心就转变成了如何实现这么一个效果。看似复杂,其实也很好做。

首先,我们重新改造一下上述的 .g-bubbles

  1. 生成 N 个一样大小的小球元素,定位在整个容器的中间
HTML:
<div class="g-container">
  <div class="g-circle"></div>
  <ul class="g-bubbles">
    <li class="g-bubble"></li>
    // ... 共 200 个 bubble 元素 
    <li class="g-bubble"></li>
  </ul>
</div>

CSS:

.g-bubbles 
    position: absolute;
    width: 30px;
    height: 30px;
    transform: translate(-50%, -50%);
    left: 50%;
    top: 50%;
    border-radius: 50px;

.g-bubble 
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: hsl(60, 75%, 40%);

得到这么一个效果,所有圆形小点,都暂时汇聚在容器的中心:

这里需要简单解释一下:

其次,我们借助 SASS,按照元素的顺序,把它们顺序排列到圆环轨迹之上:

$count: 200;
@for $i from 1 through $count  
    .g-bubble:nth-child(#$i) 
        --rotate: calc(#360 / $count * #$i * 1deg);
        transform: 
            rotateZ(var(--rotate)) 
            translate(135px, 0);
        opacity: 1;
    

由于我们设置了 div 小球的个数为 200 个,这样,我们就得到了一圈由 200 个圆形小球形成的圆环:

接下来这一步非常重要,我们设定一个动画:

  1. 让每个小球在动画的 75% ~ 100% 阶段做透明度从 1 到 0 的变换,而 0% ~ 75% 的阶段保持透明度为 0
  2. 让 200 个 div 依次进行这个动画效果(利用负的 animation-delay,从 -0 到 -4000ms),整体上就能形成逐渐消失的效果
@for $i from 1 through $count  
    .g-bubble:nth-child(#$i) 
        --rotate: calc(#360 / $count * #$i * 1deg);
        --delayTime: calc(4000 * #$i / $count * -1ms);
        transform: 
            rotateZ(var(--rotate)) 
            translate(135px, 0);
        opacity: 1;
        animation: showAndHide 4000ms linear var(--delayTime) infinite;
    

@keyframes showAndHide 
    0% 
        opacity: 0;
    
    75% 
        opacity: 0;
    
    75.1% 
        opacity: 1;
    
    100% 
        opacity: 0;
    

这样,我们就得到了一个圆形小球气泡围绕圆环渐次消失的效果:

配合上整个圆环,效果就会是这样:

很接近了,但是没有随机的感觉,气泡也没有散开的动画。解决的方案:

  1. 所以我们需要让气泡在执行透明度变化的同时,进行一个随机的发散位移
  2. 小圆形气泡的大小也可以带上一点随机,同时,在动画过程逐渐缩小

当然,整个动画的基础,还是在容器设置了 滤镜 blur() 和 contrast() 的加持之下的,这样,我们给气泡再补上随机动画散开及缩放的动画:

@for $i from 1 through $count  
    .g-bubble:nth-child(#$i) 
        --rotate: calc(#360 / $count * #$i * 1deg);
        --delayTime: calc(4000 * #$i / $count * -1ms);
        --scale: #0.4 + random(10) / 10;
        --x: #-100 + random(200)px;
        --y: #-100 + random(200)px;
        transform: 
            rotateZ(var(--rotate)) 
            translate(135px, 0);
        opacity: 1;
        animation: showAndHide 4000ms linear var(--delayTime) infinite;
    


@keyframes showAndHide 
    0% 
        transform: 
            rotateZ(var(--rotate)) 
            translate(135px, 0);
        opacity: 0;
    
    75% 
        opacity: 0;
    
    75.1% 
        transform: 
            rotateZ(var(--rotate)) 
            translate(135px, 0)
            scale(var(--scale));
        opacity: 1;
    
    100% 
        transform: 
            rotateZ(var(--rotate)) 
            translate(calc(135px + var(--x)), var(--y))
            scale(.2);
        opacity: 0;
    

只看一圈的气泡圆形,我们能得到了这样的效果:

配合上圆环的效果:

配合上父容器的 filter: hue-rotate() 动画,就能实现颜色的动态变换,得到我们最终想要的效果:

这样,没有了第一版本的违和感,整个效果也显得比较自然。

整体代码:

HTML:

<div class="g-container">
  <div class="g-circle"></div>
  <ul class="g-bubbles">
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
    <li class="g-bubble"></li>
  </ul>
</div>

CSS:

body, html 
    width: 100%;
    height: 100%;
    display: grid;
    place-content: center;
    background: #000;
    overflow: hidden;

$count: 200;

:root 
    --headColor: hsl(130, 75%, 75%);
    --endColor: hsl(60, 75%, 40%);


.g-container 
    position: relative;
    width: 300px;
    height: 300px;
    padding: 100px;
    filter: blur(3px) contrast(3);
    background: #000;
    animation: hueRotate 8s infinite linear;


.g-circle 
    position: relative;
    width: 300px;
    height: 300px;
    border-radius: 50%;
    background: conic-gradient(
        var(--headColor) 0, 
        var(--headColor) 2%,
        hsl(120, 75%, 70%), 
        hsl(110, 75%, 65%), 
        hsl(100, 75%, 60%),
        hsl(90, 75%, 55%), 
        hsl(80, 75%, 50%),
        hsl(70, 75%, 45%),
        var(--endColor) 16%,
        var(--endColor) 18%,
        transparent 18%
    );
    mask: radial-gradient(transparent, transparent 119px, #000 120px, #000);
    -webkit-mask: radial-gradient(transparent, transparent 119px, #000 120px, #000);
    animation: rotate 4s infinite -700ms linear;
    
    &::before,
    &::after 
        content: "";
        position: absolute;
        inset: 0;
        width: 32px;
        height: 32px;
        background: var(--headColor);
        top: 0;
        left: 135px;
        border-radius: 50%;
    
    
    &::after 
        background: var(--endColor);
        left: unset;
        top: 80px;
        right: 10px;
    


.g-bubbles 
    position: absolute;
    width: 30px;
    height: 30px;
    transform: translate(-50%, -50%);
    left: 50%;
    top: 50%;
    border-radius: 50px;


.g-bubble 
    position: absolute;
    border-radius: 50%;
    background: var(--endColor);


@for $i from 1 through $count  
    .g-bubble:nth-child(#$i) 
        --rotate: calc(#360 / $count * #$i * 1deg);
        --delayTime: calc(4000 * #$i / $count * -1ms);
        --width: 30px;
        --scale: #0.4 + random(10) / 10;
        --x: #-100 + random(200)px;
        --y: #-100 + random(200)px;
        width: var(--width);
        height: var(--width);
        transform: 
            rotateZ(var(--rotate)) 
            translate(135px, 0);
        opacity: 1;
        animation: showAndHide 4000ms linear var(--delayTime) infinite;
    


@keyframes showAndHide 
    0% 
        transform: 
            rotateZ(var(--rotate)) 
            translate(135px, 0);
        opacity: 0;
    
    75% 
        opacity: 0;
    
    75.1% 
        transform: 
            rotateZ(var(--rotate)) 
            translate(135px, 0)
            scale(var(--scale));
        opacity: 1;
    
    100% 
        transform: 
            rotateZ(var(--rotate)) 
            translate(calc(135px + var(--x)), var(--y))
            scale(.2);
        opacity: 0;
    


@keyframes rotate 
    100% 
        transform: rotate(-360deg);
    


@keyframes hueRotate 
    100% 
        filter: blur(3px) contrast(3) hue-rotate(360deg);
    

本文转载于:

https://juejin.cn/post/7221320687430942781

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

气泡效果中的 iOS 图像

【中文标题】气泡效果中的 iOS 图像【英文标题】:iOS images in a bubble effect 【发布时间】:2012-07-23 14:06:26 【问题描述】:

我有一些图像想要“放入气泡中”。气泡在屏幕周围漂浮,这些图像被困在其中。

最好的方法是将内部图像与气泡图像结合起来,并以某种方式扭曲内部图像,使其看起来像是在气泡内部的反射。

有谁知道如何在不使用纹理和网格的情况下实现这种效果?也许有人记得一个旧项目或做过类似事情的事情?

这是我的意思的一个例子:

【问题讨论】:

根据CoreImage 引用,过滤器CIGlassLozenge 在iOS 上不可用。因此,它不会让您的生活更轻松,因为您必须找到第 3 部分库或代码片段才能达到此效果。 【参考方案1】:

您可以使用我的开源 GPUImage 框架中的 GPUImageSphereRefractionFilter 执行此操作:

我在this answer 中详细描述了它是如何工作的,以回答关于对 Android 的类似影响的问题。基本上,我使用片段着色器来折射穿过假想球体的光,然后使用它来查找包含源图像的纹理。使用简单的高斯模糊对背景进行模糊处理。

如果您想获得所显示图像的确切外观,您可能需要调整此片段着色器以向球体添加一些掠角颜色,但这应该会让您相当接近。

为了好玩,我决定尝试更接近地复制上面的玻璃球。我在球体上添加了掠射角光照和镜面光照反射,以及不反转折射纹理坐标,导致了这个结果:

我在这个较新的版本中使用了以下片段着色器:

 varying highp vec2 textureCoordinate;

 uniform sampler2D inputImageTexture;

 uniform highp vec2 center;
 uniform highp float radius;
 uniform highp float aspectRatio;
 uniform highp float refractiveIndex;
// uniform vec3 lightPosition;
 const highp vec3 lightPosition = vec3(-0.5, 0.5, 1.0);
 const highp vec3 ambientLightPosition = vec3(0.0, 0.0, 1.0);

 void main()
 
     highp vec2 textureCoordinateToUse = vec2(textureCoordinate.x, (textureCoordinate.y * aspectRatio + 0.5 - 0.5 * aspectRatio));
     highp float distanceFromCenter = distance(center, textureCoordinateToUse);
     lowp float checkForPresenceWithinSphere = step(distanceFromCenter, radius);

     distanceFromCenter = distanceFromCenter / radius;

     highp float normalizedDepth = radius * sqrt(1.0 - distanceFromCenter * distanceFromCenter);
     highp vec3 sphereNormal = normalize(vec3(textureCoordinateToUse - center, normalizedDepth));

     highp vec3 refractedVector = 2.0 * refract(vec3(0.0, 0.0, -1.0), sphereNormal, refractiveIndex);
     refractedVector.xy = -refractedVector.xy;

     highp vec3 finalSphereColor = texture2D(inputImageTexture, (refractedVector.xy + 1.0) * 0.5).rgb;

     // Grazing angle lighting
     highp float lightingIntensity = 2.5 * (1.0 - pow(clamp(dot(ambientLightPosition, sphereNormal), 0.0, 1.0), 0.25));
     finalSphereColor += lightingIntensity;

     // Specular lighting
     lightingIntensity  = clamp(dot(normalize(lightPosition), sphereNormal), 0.0, 1.0);
     lightingIntensity  = pow(lightingIntensity, 15.0);
     finalSphereColor += vec3(0.8, 0.8, 0.8) * lightingIntensity;

     gl_FragColor = vec4(finalSphereColor, 1.0) * checkForPresenceWithinSphere;
 

并且此滤镜可以使用 GPUImageGlassSphereFilter 运行。

【讨论】:

我只是在看这个过滤器。完美,谢谢布拉德。 有没有办法关闭背景图,所以我只有反射球图? 模糊背景不是默认滤镜的一部分,它只是生成球体。在 FilterShowcase 示例中(我从中提取了上面的内容),我使用了一个高斯模糊滤镜和一个混合来组合两个过滤后的图像。您可以只使用球体折射滤镜来仅获取中心球体图像。 布拉德,无论中心如何,这似乎总是显示中心坐标,无论中心在哪里。例如,如果我移动中心,图像中心的像素会随着玻璃球体移动。那有意义吗?有没有办法使用当前中心周围的像素而不是图像中心来创建玻璃球?【参考方案2】:

作为记录,我最终使用了@BradLarson 建议的 GPUImage,但我必须编写一个自定义过滤器,如下所示。该滤镜采用“内部”图像和气泡纹理并将两者混合,同时还执行折射计算但不反转图像坐标。效果:

.h

@interface GPUImageBubbleFilter : GPUImageTwoInputFilter

@property (readwrite, nonatomic) CGFloat refractiveIndex;   
@property (readwrite, nonatomic) CGFloat radius;            

@end

.m

#import "GPUImageBubbleFilter.h"

NSString *const kGPUImageBubbleShaderString = SHADER_STRING
(
 varying highp vec2 textureCoordinate;
 varying highp vec2 textureCoordinate2;

 uniform sampler2D inputImageTexture;
 uniform sampler2D inputImageTexture2;

 uniform highp vec2 center;
 uniform highp float radius;
 uniform highp float aspectRatio;
 uniform highp float refractiveIndex;

 void main()
 
     highp vec2 textureCoordinateToUse = vec2(textureCoordinate.x, (textureCoordinate.y * aspectRatio + 0.5 - 0.5 * aspectRatio));
     highp float distanceFromCenter = distance(center, textureCoordinateToUse);
     lowp float checkForPresenceWithinSphere = step(distanceFromCenter, radius);

     distanceFromCenter = distanceFromCenter / radius;

     highp float normalizedDepth = radius * sqrt(1.0 - distanceFromCenter * distanceFromCenter);
     highp vec3 sphereNormal = normalize(vec3(textureCoordinateToUse - center, normalizedDepth));

     highp vec3 refractedVector = refract(vec3(0.0, 0.0, -1.0), sphereNormal, refractiveIndex);

     lowp vec4 textureColor = texture2D(inputImageTexture, (refractedVector.xy + 1.0) * 0.5) * checkForPresenceWithinSphere; 
     lowp vec4 textureColor2 = texture2D(inputImageTexture2, textureCoordinate2) * checkForPresenceWithinSphere;

     gl_FragColor = mix(textureColor, textureColor2, textureColor2.a);    
 

 );


@interface GPUImageBubbleFilter () 
    GLint radiusUniform, centerUniform, aspectRatioUniform, refractiveIndexUniform;


@property (readwrite, nonatomic) CGFloat aspectRatio;

@end

@implementation GPUImageBubbleFilter
@synthesize radius = _radius, refractiveIndex = _refractiveIndex, aspectRatio = _aspectRatio;

- (id) init 
    self = [super initWithFragmentShaderFromString: kGPUImageBubbleShaderString];
    if( self ) 
        radiusUniform = [filterProgram uniformIndex: @"radius"];
        aspectRatioUniform = [filterProgram uniformIndex: @"aspectRatio"];
        centerUniform = [filterProgram uniformIndex: @"center"];
        refractiveIndexUniform = [filterProgram uniformIndex: @"refractiveIndex"];

        self.radius = 0.5;
        self.refractiveIndex = 0.5;
        self.aspectRatio = 1.0;

        GLfloat center[2] = 0.5, 0.5;
        [GPUImageOpenGLESContext useImageProcessingContext];
        [filterProgram use];
        glUniform2fv(centerUniform, 1, center);

        [self setBackgroundColorRed: 0 green: 0 blue: 0 alpha: 0];
    

    return self;


#pragma mark - Accessors
- (void) setRadius:(CGFloat)radius 
    _radius = radius;

    [GPUImageOpenGLESContext useImageProcessingContext];
    [filterProgram use];
    glUniform1f(radiusUniform, _radius);


- (void) setAspectRatio:(CGFloat)aspectRatio 
    _aspectRatio = aspectRatio;

    [GPUImageOpenGLESContext useImageProcessingContext];
    [filterProgram use];
    glUniform1f(aspectRatioUniform, _aspectRatio);


- (void)setRefractiveIndex:(CGFloat)newValue;

    _refractiveIndex = newValue;

    [GPUImageOpenGLESContext useImageProcessingContext];
    [filterProgram use];
    glUniform1f(refractiveIndexUniform, _refractiveIndex);

【讨论】:

我想看看我是否可以单独使用一些照明计算来更接近你的气泡效果和上面的玻璃球,我已经更新了上面的答案。您的气泡纹理可能仍会产生更美观的结果,但您可以尝试 GPUImageGlassSphereFilter 看看它是否可以在您的应用程序中正常工作。 承认吧,这很有趣不是吗:)

以上是关于记录-有意思的气泡 Loading 效果的主要内容,如果未能解决你的问题,请参考以下文章

使用纯 CSS 实现超酷炫的粘性气泡效果

气泡效果中的 iOS 图像

loading operating system是啥意思

纯css实现loading加载中(多种展现形式)

如何用css3实现这个有意思的loading动画

巧用 CSS 实现动态线条 Loading 动画