"Clipping"背景,以便在堆叠上下文中查看自身下方



[ 注意:寻找一种跨浏览器解决方案,该解决方案不会在每一波粘液之间瞬间闪烁身体的背景,如ccprog的答案所示;理想情况下,解决方案不应涉及等到第一波结束才开始显示第二波,以便两个波可以同时运行。我愿意放弃动态随机化的goop以获得理想的解决方案。]

有谁知道我如何使第二波橙色粘稠(.goo-two)"切开"第一波棕色粘稠(.goo-one)和天蓝色容器(.goo-container)以显示或暴露红色主体元素(body),或者就此而言,在堆叠上下文中它下面的任何其他元素?可能吗?

值得注意的是,我给容器(.goo-container)一个坚实的背景的原因是因为我用它来掩盖网站其余部分的加载过程,我希望橙色粘稠(.goo-two)可以用来显示内容。它变得更加棘手,因为橙色粘稠物在棕色粘液完成之前开始滴落,这将是将 contianer (.goo-container) 的背景从skyblue更改为transparent的最佳时机,尽管半透明渐变作为背景可能仍然可以用来实现这一目标。(要么是这样,要么是完全不同的东西,比如复制橙色图层并使用一个来裁剪棕色路径,另一个来裁剪天蓝色图层。

有什么想法吗?

const
gooCont = document.querySelector('div.goo-container'),
gooOne = gooCont.querySelector('div.goo-one'),
gooTwo = gooCont.querySelector('div.goo-two'),
rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min
gooCont.style.setProperty('--translateY', `translateY(-${innerWidth * 0.21 / innerHeight * 100 + 100}%)`)
generateGoo(gooOne)
function generateGoo(goo) {
const
randQty = rand(20,30),
unit = innerWidth / (randQty - 1) / innerWidth * 100
if (getComputedStyle(goo).display === 'none') goo.style.display = 'block'
for (let i = 0; i < randQty; i++) {
const
div = document.createElement('div'),
minWidthPx = innerWidth < 500 ? innerWidth * 0.1 : innerWidth * 0.05,
minMaxWidthPx = innerWidth < 500 ? innerWidth * 0.2 : innerWidth * 0.1,
widthPx = rand(minWidthPx, minMaxWidthPx),
widthPerc = widthPx / innerWidth * 100,
heightPx = rand(widthPx / 2, widthPx * 3),
heightPerc = heightPx / gooCont.getBoundingClientRect().height * 100,
translateY = rand(45, 70),
targetTranslateY = rand(15, 100),
borderRadiusPerc = rand(40, 50)
div.style.width = widthPerc + '%'
div.style.height = heightPerc + '%'
div.style.left = i * unit + '%'
div.style.transform = `translate(-50%, ${translateY}%)`
div.style.borderRadius = borderRadiusPerc + '%'
div.setAttribute('data-translate', targetTranslateY)
goo.appendChild(div)
}
goo.style.transform = `translateY(0)`
goo.childNodes.forEach(
v => v.style.transform = `translateY(${v.getAttribute('data-translate')}%)`
)
}
setTimeout(() => {
gooTwo.innerHTML = ''
generateGoo(gooTwo)
}, 2300)
html,
body {
width: 100%;
height: 100%;
margin: 0;
background: red;
}
div.goo-container {
--translateY: translateY(-165%);
z-index: 1;
width: 100%;
height: 100%;
position: fixed;
overflow: hidden;
background: skyblue;
}
div.goo-container > div.goo-one,
div.goo-container > div.goo-two {
width: 100%;
height: 100%;
position: absolute;
transform: var(--translateY);
filter: url('#goo-filter');
background: #5b534a;
transition: transform 2.8s linear;
}
div.goo-container > div.goo-one > div,
div.goo-container > div.goo-two > div {
position: absolute;
bottom: 0;
background: #5b534a;
transition: transform 2.8s linear;
}
div.goo-container > div.goo-two {
display: none;
transition: transform 2.8s linear;
}
div.goo-container > div.goo-two,
div.goo-container > div.goo-two > div {
background: orange;
}
svg {
/* Prevents effect on Firefox */
/* display: none; */
}
<div class='goo-container'>
<div class='goo-one'></div>
<div class='goo-two'></div>
</div>
<svg xmlns='http://www.w3.org/2000/svg' version='1.1'>
<defs>
<filter id='goo-filter'>
<feGaussianBlur in='SourceGraphic' stdDeviation='10' result='blur' />
<feColorMatrix in='blur' mode='matrix' values='1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 18 -7' result='goo' />
<feBlend in='SourceGraphic' in2='goo' />
</filter>
</defs>
</svg>

我很确定这不是最佳变体,但它似乎有效,至少在 Firefox 中是这样。Chrome 在动画每个部分的初始帧方面存在一些问题。

  • 我稍微重写了粘稠的过滤器代码,以提高可读性,同时保持相同的效果。有关解释,请参阅我的文章。
  • 只有.goo-one和子div 才能获得背景颜色。这使得.goo-two变得透明成为可能。
  • 这两个部分获得不同的过滤器,但过滤器区域垂直增加,以便在过渡开始时到达屏幕底部。
  • 第一个滤镜将天蓝色作为背景填充。
  • 第二个过滤器有一个棕色填充,但它的应用是倒置的:它只显示在粘性区域之外,使内部区域为空。构成粘性区域的div 矩形不会跨越整个.gooTwo。要填充(倒置后为空)顶部,需要额外的<div class="first">
  • 在第二个粘性部分的过渡开始时,过滤器区域上限设置为低于屏幕下限边界。这会隐藏天蓝色背景,同时第二个粘性部分变得可见。
  • 请注意,svg元素的 CSS 略有变化,以获得更好的浏览器兼容性。
  • 作为概念证明,一些内容被添加到容器div 中。它表明需要pointer-event: none;否则将无法与页面进行交互。

const
gooCont = document.querySelector('div.goo-container'),
gooOne = gooCont.querySelector('div.goo-one'),
gooTwo = gooCont.querySelector('div.goo-two'),
filterOne = document.querySelector('#goo-filter-one')
rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min
gooCont.style.setProperty('--translateY', `translateY(-${innerWidth * 0.21 / innerHeight * 100 + 100}%)`)
generateGoo(gooOne)
function generateGoo(goo) {
const
randQty = rand(20,30),
unit = innerWidth / (randQty - 1) / innerWidth * 100
if (getComputedStyle(goo).display === 'none') goo.style.display = 'block'
goo.removeAttribute('y')
for (let i = 0; i < randQty; i++) {
const
div = document.createElement('div'),
minWidthPx = innerWidth < 500 ? innerWidth * 0.1 : innerWidth * 0.05,
minMaxWidthPx = innerWidth < 500 ? innerWidth * 0.2 : innerWidth * 0.1,
widthPx = rand(minWidthPx, minMaxWidthPx),
widthPerc = widthPx / innerWidth * 100,
heightPx = rand(widthPx / 2, widthPx * 3),
heightPerc = heightPx / gooCont.getBoundingClientRect().height * 100,
translateY = rand(45, 70),
targetTranslateY = rand(15, 100),
borderRadiusPerc = rand(40, 50)
div.style.width = widthPerc + '%'
div.style.height = heightPerc + '%'
div.style.left = i * unit + '%'
div.style.transform = `translate(-50%, ${translateY}%)`
div.style.borderRadius = borderRadiusPerc + '%'
div.setAttribute('data-translate', targetTranslateY)
goo.appendChild(div)
}
goo.style.transform = `translateY(0)`
goo.childNodes.forEach(
v => v.style.transform = `translateY(${v.getAttribute('data-translate')}%)`
)
}
setTimeout(() => {
gooTwo.innerHTML = '<div class="first"></div>'
filterOne.setAttribute('y', '100%')
generateGoo(gooTwo, true)
}, 2300)
html,
body {
width: 100%;
height: 100%;
margin: 0;
background: red;
}
div.goo-container {
--translateY: translateY(-165%);
z-index: 1;
width: 100%;
height: 100%;
position: fixed;
overflow: hidden;
}
div.goo-container > div {
width: 100%;
height: 100%;
position: absolute;
pointer-events: none;
transform: var(--translateY);
transition: transform 2.8s linear;
}
div.goo-container > div.goo-one {
filter: url('#goo-filter-one');
background: #5b534a;
}
div.goo-container > div.goo-two {
display: none;
filter: url('#goo-filter-two');
}
div.goo-container > div.goo-one > div,
div.goo-container > div.goo-two > div {
position: absolute;
bottom: 0;
background: #5b534a;
transition: transform 2.8s linear;
}
div.goo-container > div.goo-two > div.first {
top: -10%;
width: 100%;
height: 110%;
}
svg {
width: 0;
height: 0;
}
<div class='goo-container'>
<div class='goo-one'></div>
<div class='goo-two'></div>
<p><a href="#">Click me</a> and read.</p>
</div>
<svg xmlns='http://www.w3.org/2000/svg' version='1.1'>
<filter id='goo-filter-one' height='200%'>
<feGaussianBlur in='SourceGraphic' stdDeviation='10' result='blur' />
<feComponentTransfer in='blur' result='goo'>
<feFuncA type='linear' slope='18' intercept='-7' />
</feComponentTransfer>
<feFlood flood-color='skyblue' result='back' />
<feMerge>
<feMergeNode in='back' />
<feMergeNode in='goo' />
</feMerge>
</filter>
<filter id='goo-filter-two' height='200%'>
<feGaussianBlur in='SourceGraphic' stdDeviation='10' result='blur' />
<feComponentTransfer in='blur' result='goo'>
<feFuncA type='linear' slope='18' intercept='-7' />
</feComponentTransfer>
<feFlood flood-color='#5b534a' result='back' />
<feComposite operator='out' in='back' in2='goo' />
</filter>
</svg>

首先,我将开始使用一个div 和多个渐变来构建形状。

这是一个使用不固定渐变(相同宽度和不同高度)的想法,我们可以轻松定位:

:root {
--c:linear-gradient(red,red);
}
div.goo-container {
position:fixed;
top:0;
left:-20px;
right:-20px;
height:200px;
background:
var(--c) calc(0*100%/9) 0/calc(100%/10) 80%,
var(--c) calc(1*100%/9) 0/calc(100%/10) 60%,
var(--c) calc(2*100%/9) 0/calc(100%/10) 30%,
var(--c) calc(3*100%/9) 0/calc(100%/10) 50%,
var(--c) calc(4*100%/9) 0/calc(100%/10) 59%,
var(--c) calc(5*100%/9) 0/calc(100%/10) 48%,
var(--c) calc(6*100%/9) 0/calc(100%/10) 36%,
var(--c) calc(7*100%/9) 0/calc(100%/10) 70%,
var(--c) calc(8*100%/9) 0/calc(100%/10) 75%,
var(--c) calc(9*100%/9) 0/calc(100%/10) 35%;
background-repeat:no-repeat;
filter: url('#goo-filter');
}
<div class='goo-container'>
</div>

<svg xmlns='http://www.w3.org/2000/svg' version='1.1'>
<defs>
<filter id='goo-filter'>
<feGaussianBlur in='SourceGraphic' stdDeviation='10' result='blur' />
<feColorMatrix in='blur' mode='matrix' values='1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 20 -5' result='goo' />
<feBlend in='SourceGraphic' in2='goo' />
</filter>
</defs>
</svg>

我们也可以有可变宽度,这里需要 JS 来生成所有这些:

:root {
--c:linear-gradient(red,red);
}
div.goo-container {
position:fixed;
top:0;
left:-20px;
right:-20px;
height:200px;
background:
var(--c) 0     0/20px 80%,
var(--c) 20px  0/80px 60%,
var(--c) 100px 0/10px 30%,
var(--c) 110px 0/50px 50%,
var(--c) 160px 0/30px 59%,
var(--c) 190px 0/80px 48%,
var(--c) 270px 0/10px 36%,
var(--c) 280px 0/20px 70%,
var(--c) 300px 0/50px 75%,
var(--c) 350px 0/80px 35%
/* and so on ... */;
background-repeat:no-repeat;
filter: url('#goo-filter');
}
<div class='goo-container'>
</div>

<svg xmlns='http://www.w3.org/2000/svg' version='1.1'>
<defs>
<filter id='goo-filter'>
<feGaussianBlur in='SourceGraphic' stdDeviation='10' result='blur' />
<feColorMatrix in='blur' mode='matrix' values='1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 20 -5' result='goo' />
<feBlend in='SourceGraphic' in2='goo' />
</filter>
</defs>
</svg>

然后使用更多的CSS,我们可以拥有第一个动画:

:root {
--c:linear-gradient(red,red);
}
div.goo-container {
position:fixed;
height:100vh;
top:0;
left:0;
right:0;
background:red;
transform:translateY(-150vh);
animation:move 3s 1s forwards;
}
div.goo-container::after {
position:absolute;
content:"";
top:100%;
left:-20px;
right:-20px;
height:50vh;
margin:0 -20px;
background:
var(--c) calc(0*100%/9) 0/calc(100%/10) 80%,
var(--c) calc(1*100%/9) 0/calc(100%/10) 60%,
var(--c) calc(2*100%/9) 0/calc(100%/10) 30%,
var(--c) calc(3*100%/9) 0/calc(100%/10) 50%,
var(--c) calc(4*100%/9) 0/calc(100%/10) 59%,
var(--c) calc(5*100%/9) 0/calc(100%/10) 48%,
var(--c) calc(6*100%/9) 0/calc(100%/10) 36%,
var(--c) calc(7*100%/9) 0/calc(100%/10) 70%,
var(--c) calc(8*100%/9) 0/calc(100%/10) 75%,
var(--c) calc(9*100%/9) 0/calc(100%/10) 35%;
background-repeat:no-repeat;
filter: url('#goo-filter');
}
div.goo-container::before {
position:absolute;
content:"";
top:100%;
height:150vh;
background:blue;
left:0;
right:0;
}
@keyframes move {
to {
transform:translateY(0);
}
}
<div class='goo-container'>
</div>

<svg xmlns='http://www.w3.org/2000/svg' version='1.1'>
<defs>
<filter id='goo-filter'>
<feGaussianBlur in='SourceGraphic' stdDeviation='10' result='blur' />
<feColorMatrix in='blur' mode='matrix' values='1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 20 -5' result='goo' />
<feBlend in='SourceGraphic' in2='goo' />
</filter>
</defs>
</svg>

仍然不完美,但我们也可以添加一些渐变动画来调整大小:

:root {
--c:linear-gradient(red,red);
}
div.goo-container {
position:fixed;
height:100vh;
top:0;
left:0;
right:0;
background:red;
transform:translateY(-150vh);
animation:move 5s 0.5s forwards;
}
div.goo-container::after {
position:absolute;
content:"";
top:100%;
left:-20px;
right:-20px;
height:50vh;
margin:0 -20px;
background:
var(--c) calc(0*100%/9) 0/calc(100%/10) 80%,
var(--c) calc(1*100%/9) 0/calc(100%/10) 60%,
var(--c) calc(2*100%/9) 0/calc(100%/10) 30%,
var(--c) calc(3*100%/9) 0/calc(100%/10) 50%,
var(--c) calc(4*100%/9) 0/calc(100%/10) 59%,
var(--c) calc(5*100%/9) 0/calc(100%/10) 48%,
var(--c) calc(6*100%/9) 0/calc(100%/10) 36%,
var(--c) calc(7*100%/9) 0/calc(100%/10) 70%,
var(--c) calc(8*100%/9) 0/calc(100%/10) 75%,
var(--c) calc(9*100%/9) 0/calc(100%/10) 35%;
background-repeat:no-repeat;
filter: url('#goo-filter');
animation:grad 4.5s 1s forwards;
}
div.goo-container::before {
position:absolute;
content:"";
top:100%;
height:150vh;
background:blue;
left:0;
right:0;
}
@keyframes move {
to {
transform:translateY(0);
}
}
@keyframes grad {
to {
background-size:
calc(100%/10) 50%,
calc(100%/10) 75%,
calc(100%/10) 20%,
calc(100%/10) 60%,
calc(100%/10) 55%,
calc(100%/10) 80%,
calc(100%/10) 23%,
calc(100%/10) 80%,
calc(100%/10) 90%,
calc(100%/10) 20%;
}
}
<div class='goo-container'>
</div>

<svg xmlns='http://www.w3.org/2000/svg' version='1.1'>
<defs>
<filter id='goo-filter'>
<feGaussianBlur in='SourceGraphic' stdDeviation='10' result='blur' />
<feColorMatrix in='blur' mode='matrix' values='1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 20 -5' result='goo' />
<feBlend in='SourceGraphic' in2='goo' />
</filter>
</defs>
</svg>

以上有点棘手,因为每个梯度的位置将取决于所有前一个梯度的大小(这里可能需要JS或SASS来生成代码)


对于第二个动画,我们将执行相同的操作,但我们认为蒙版属性内的渐变层具有相反的效果(渐变层将被删除以查看剩余部分)

:root {
--c:linear-gradient(red,red);
background:pink;
}
div.goo-container {
position:fixed;
height:150vh;
top:0;
left:0;
right:0;
transform:translateY(-200vh);
animation:move 8s 0.5s forwards;
filter: url('#goo-filter');
}
div.goo-container > div {
height:100%;
background:red;
-webkit-mask:
var(--c) calc(0*100%/9) 0/calc(100%/10 + 4px) 40vh,
var(--c) calc(1*100%/9) 0/calc(100%/10 + 4px) 30vh,
var(--c) calc(2*100%/9) 0/calc(100%/10 + 4px) 15vh,
var(--c) calc(3*100%/9) 0/calc(100%/10 + 4px) 20vh,
var(--c) calc(4*100%/9) 0/calc(100%/10 + 4px) 29vh,
var(--c) calc(5*100%/9) 0/calc(100%/10 + 4px) 35vh,
var(--c) calc(6*100%/9) 0/calc(100%/10 + 4px) 12vh,
var(--c) calc(7*100%/9) 0/calc(100%/10 + 4px) 50vh,
var(--c) calc(8*100%/9) 0/calc(100%/10 + 4px) 48vh,
var(--c) calc(9*100%/9) 0/calc(100%/10 + 4px) 40vh,
linear-gradient(#fff,#fff);
-webkit-mask-composite:destination-out;
mask-composite:exclude;
-webkit-mask-repeat:no-repeat;
animation:mask 7.5s 1s forwards;
}
div.goo-container::after {
position:absolute;
content:"";
top:100%;
left:-20px;
right:-20px;
height:50vh;
margin:0 -20px;
background:
var(--c) calc(0*100%/9) 0/calc(100%/10) 80%,
var(--c) calc(1*100%/9) 0/calc(100%/10) 60%,
var(--c) calc(2*100%/9) 0/calc(100%/10) 30%,
var(--c) calc(3*100%/9) 0/calc(100%/10) 50%,
var(--c) calc(4*100%/9) 0/calc(100%/10) 59%,
var(--c) calc(5*100%/9) 0/calc(100%/10) 48%,
var(--c) calc(6*100%/9) 0/calc(100%/10) 36%,
var(--c) calc(7*100%/9) 0/calc(100%/10) 60%,
var(--c) calc(8*100%/9) 0/calc(100%/10) 65%,
var(--c) calc(9*100%/9) 0/calc(100%/10) 35%;
background-repeat:no-repeat;
filter: url('#goo-filter');
animation:grad 7.5s 1s forwards;
}
div.goo-container::before {
position:absolute;
content:"";
top:100%;
height:150vh;
background:blue;
left:0;
right:0;
}
@keyframes move {
to {
transform:translateY(150vh);
}
}
@keyframes grad {
to {
background-size:
calc(100%/10) 50%,
calc(100%/10) 75%,
calc(100%/10) 20%,
calc(100%/10) 60%,
calc(100%/10) 55%,
calc(100%/10) 80%,
calc(100%/10) 23%,
calc(100%/10) 80%,
calc(100%/10) 90%,
calc(100%/10) 20%;
}
}
@keyframes mask {
to {
-webkit-mask-size:
calc(100%/10) 30vh,
calc(100%/10) 10vh,
calc(100%/10) 50vh,
calc(100%/10) 45vh,
calc(100%/10) 12vh,
calc(100%/10) 22vh,
calc(100%/10) 60vh,
calc(100%/10) 10vh,
calc(100%/10) 8vh,
calc(100%/10) 35vh,
auto;
}
}
<h1>Lorem ipsum dolor sit amet</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eu sodales lectus. Sed non erat accumsan, placerat purus quis, sodales mi. Suspendisse potenti. Sed eu viverra odio. </p>

<div class='goo-container'>
<div></div>
</div>
<svg xmlns='http://www.w3.org/2000/svg' version='1.1'>
<defs>
<filter id='goo-filter'>
<feGaussianBlur in='SourceGraphic' stdDeviation='10' result='blur' />
<feColorMatrix in='blur' mode='matrix' values='1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 20 -5' result='goo' />
<feBlend in='SourceGraphic' in2='goo' />
</filter>
</defs>
</svg>

我们做一些代码优化,只保留一个元素:

:root {
--c:linear-gradient(red,red);
background:pink;
}
div.goo-container {
position:fixed;
top:0;
left:0;
right:0;
bottom:0;
transform:translateY(-150%);
animation:move 8s 0.5s forwards;
filter: url('#goo-filter');
}
div.goo-container::after {
position:absolute;
content:"";
top:-50%;
left:0;
right:0;
bottom:-50%;
background:
var(--c) calc(0*100%/9) 0/calc(100%/10) calc(100% - 40vh),
var(--c) calc(1*100%/9) 0/calc(100%/10) calc(100% - 30vh),
var(--c) calc(2*100%/9) 0/calc(100%/10) calc(100% - 35vh),
var(--c) calc(3*100%/9) 0/calc(100%/10) calc(100% - 50vh),
var(--c) calc(4*100%/9) 0/calc(100%/10) calc(100% - 10vh),
var(--c) calc(5*100%/9) 0/calc(100%/10) calc(100% - 15vh),
var(--c) calc(6*100%/9) 0/calc(100%/10) calc(100% - 30vh),
var(--c) calc(7*100%/9) 0/calc(100%/10) calc(100% - 28vh),
var(--c) calc(8*100%/9) 0/calc(100%/10) calc(100% - 30vh),
var(--c) calc(9*100%/9) 0/calc(100%/10) calc(100% - 50vh);
background-repeat:no-repeat;
-webkit-mask:
var(--c) calc(0*100%/9) 100%/calc(100%/10 + 4px) calc(100% - 20vh),
var(--c) calc(1*100%/9) 100%/calc(100%/10 + 4px) calc(100% - 10vh),
var(--c) calc(2*100%/9) 100%/calc(100%/10 + 4px) calc(100% - 50vh),
var(--c) calc(3*100%/9) 100%/calc(100%/10 + 4px) calc(100% - 30vh),
var(--c) calc(4*100%/9) 100%/calc(100%/10 + 4px) calc(100% - 35vh),
var(--c) calc(5*100%/9) 100%/calc(100%/10 + 4px) calc(100% - 10vh),
var(--c) calc(6*100%/9) 100%/calc(100%/10 + 4px) calc(100% - 50vh),
var(--c) calc(7*100%/9) 100%/calc(100%/10 + 4px) calc(100% - 40vh),
var(--c) calc(8*100%/9) 100%/calc(100%/10 + 4px) calc(100% - 45vh),
var(--c) calc(9*100%/9) 100%/calc(100%/10 + 4px) calc(100% - 35vh);
-webkit-mask-repeat:no-repeat;
filter: inherit;
animation: inherit;
animation-name:grad, mask;
}
div.goo-container::before {
position:absolute;
content:"";
top:50%;
bottom:-150%;
background:blue;
left:0;
right:0;
}
@keyframes move {
to {
transform:translateY(200%);
}
}
@keyframes grad {
to {
background-size:
calc(100%/10) calc(100% - 10vh),
calc(100%/10) calc(100% - 50vh),
calc(100%/10) calc(100% - 30vh),
calc(100%/10) calc(100% - 10vh),
calc(100%/10) calc(100% - 40vh),
calc(100%/10) calc(100% - 25vh),
calc(100%/10) calc(100% - 32vh),
calc(100%/10) calc(100% - 18vh),
calc(100%/10) calc(100% - 50vh),
calc(100%/10) calc(100% - 10vh);
}
}
@keyframes mask {
to {
-webkit-mask-size:
calc(100%/10) calc(100% - 10vh),
calc(100%/10) calc(100% - 50vh),
calc(100%/10) calc(100% - 10vh),
calc(100%/10) calc(100% - 30vh),
calc(100%/10) calc(100% - 32vh),
calc(100%/10) calc(100% - 40vh),
calc(100%/10) calc(100% - 50vh),
calc(100%/10) calc(100% - 25vh),
calc(100%/10) calc(100% - 18vh),
calc(100%/10) calc(100% - 10vh);
}
}
<h1>Lorem ipsum dolor sit amet</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eu sodales lectus. Sed non erat accumsan, placerat purus quis, sodales mi. Suspendisse potenti. Sed eu viverra odio. </p>

<div class='goo-container'></div>
<svg xmlns='http://www.w3.org/2000/svg' version='1.1'>
<defs>
<filter id='goo-filter'>
<feGaussianBlur in='SourceGraphic' stdDeviation='10' result='blur' />
<feColorMatrix in='blur' mode='matrix' values='1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 20 -5' result='goo' />
<feBlend in='SourceGraphic' in2='goo' />
</filter>
</defs>
</svg>

最后是使用 SASS 生成梯度和遮罩层的动态解决方案:https://codepen.io/t_afif/pen/oNzxYgV

更新

另一个不使用面具的想法。诀窍是使渐变居中。该解决方案将获得更多支持,但底部和顶部形状都将是对称的

:root {
--c:linear-gradient(red,red);
background:pink;
}
div.goo-container {
position:fixed;
top:0;
left:0;
right:0;
bottom:0;
transform:translateY(-150%);
animation:move 8s 0.5s forwards;
filter: url('#goo-filter');
}
div.goo-container::after {
position:absolute;
content:"";
top:-50%;
left:0;
right:0;
bottom:-50%;
background:
var(--c) calc(0*100%/9) 50%/calc(100%/10) calc(100% - 80vh),
var(--c) calc(1*100%/9) 50%/calc(100%/10) calc(100% - 60vh),
var(--c) calc(2*100%/9) 50%/calc(100%/10) calc(100% - 70vh),
var(--c) calc(3*100%/9) 50%/calc(100%/10) calc(100% - 100vh),
var(--c) calc(4*100%/9) 50%/calc(100%/10) calc(100% - 20vh),
var(--c) calc(5*100%/9) 50%/calc(100%/10) calc(100% - 30vh),
var(--c) calc(6*100%/9) 50%/calc(100%/10) calc(100% - 60vh),
var(--c) calc(7*100%/9) 50%/calc(100%/10) calc(100% - 56vh),
var(--c) calc(8*100%/9) 50%/calc(100%/10) calc(100% - 60vh),
var(--c) calc(9*100%/9) 50%/calc(100%/10) calc(100% - 100vh);
background-repeat:no-repeat;
filter: inherit;
animation:grad 8s 0.5s forwards;
}
div.goo-container::before {
position:absolute;
content:"";
top:50%;
bottom:-150%;
background:blue;
left:0;
right:0;
}
@keyframes move {
to {
transform:translateY(200%);
}
}
@keyframes grad {
to {
background-size:
calc(100%/10) calc(100% - 20vh),
calc(100%/10) calc(100% - 100vh),
calc(100%/10) calc(100% - 60vh),
calc(100%/10) calc(100% - 20vh),
calc(100%/10) calc(100% - 80vh),
calc(100%/10) calc(100% - 50vh),
calc(100%/10) calc(100% - 64vh),
calc(100%/10) calc(100% - 34vh),
calc(100%/10) calc(100% - 100vh),
calc(100%/10) calc(100% - 20vh);
}
}
<h1>Lorem ipsum dolor sit amet</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eu sodales lectus. Sed non erat accumsan, placerat purus quis, sodales mi. Suspendisse potenti. Sed eu viverra odio. </p>

<div class='goo-container'></div>
<svg xmlns='http://www.w3.org/2000/svg' version='1.1'>
<defs>
<filter id='goo-filter'>
<feGaussianBlur in='SourceGraphic' stdDeviation='10' result='blur' />
<feColorMatrix in='blur' mode='matrix' values='1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 20 -5' result='goo' />
<feBlend in='SourceGraphic' in2='goo' />
</filter>
</defs>
</svg>

和 SASS 版本:https://codepen.io/t_afif/pen/wvzGoeJ

这里试图避免所有滤镜、遮罩和合成困难。它只是一些贝塞尔路径的 SMIL 动画,应该支持它而没有任何错误。我还没有找到第一波和第二波同时出现在屏幕上的解决方案。

我承认最费力的部分是为路径设计算法,其他一切都相对简单。

"goo"是具有上下边框的区域,在工作区中移动,同时路径的形式发生变化。我试图在代码注释中描述哪些部分可以调整。路径组合的基本结构保证了一个重要的限制:对于动画的不同关键帧,路径作为一个整体不能具有不同的路径命令序列,否则平滑动画将失败。更改数字应该没有问题。

粘液后面是一个不透明的矩形,最初隐藏了内容。它在适当的时间隐藏,而粘液在屏幕上运行。

动画的计时在<set><animate>元素的属性中定义。请注意,goo 动画运行 6 秒,而背景矩形的隐藏发生在 3 秒后。此分布与<animate keyTimes>属性的值匹配:0;0.5;1,您可以将其读取为 0%、50%、100% 作为关键帧的计时。<set>触发器的时间必须与中间关键帧匹配,因为这是粘性覆盖整个工作区的时间。

const
rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min,
flatten = (x, y) => `${x.toFixed(2)},${y.toFixed(2)}`
function randomPoints(width, height) {
const
from = [],
to = []
let x = 0, old_extent = 0
while (x + old_extent < width) {
//width of a single goo tongue
const extent = rand(5, 20)
// rand() part: distance between tongues
x += (from.length ? 1.5 : 0) * (old_extent + extent) + rand(0, 5)
const data = {
x1: x - extent,
x2: x + extent,
// "roundness": how far will the lowest point of the tongue
// stretch below its defining line (qualitative value)
dty: extent * rand(0.4, 1.4)
}
// y: tongue postition above screen border at start
// Note the -20 gives space for the "roundness" not to cross the threshold
from.push({ ...data, y: rand(-50, -20) })
// y: tongue postition below screen border at end
// Note the 10 gives space for the "roundness" not to cross the threshold
to.push({ ...data, y: rand(10, 105) + height })
old_extent = extent
}
return { from, to }
}
function generatePath(points, path, back) {
const qti = points.length
let old_dtx, old_dty
if (back) points.reverse()
for (let i = 0; i < qti; i++) {
const
x1 = back ? points[i].x2 : points[i].x1,
x2 = back ? points[i].x1 : points[i].x2,
dtx = (x2 - x1) / 2
let dty = 0
if (i == 0) {
path.push(
back ? 'L' : 'M', 
flatten(x1, points[i].y), 
'Q',
flatten(x1 + dtx, points[i].y),        
flatten(x2, points[i].y)
);
} else {
if (i !== qti - 1) {
const
y0 = points[i - 1].y,
y1 = points[i].y,
y2 = points[i + 1].y,
// the numbers give a weight to the "roundness" value for different cases:
// a tongue stretching below its neighbors = 1 (rounding downwards)
// a tongue laging behind below its neighbors = -0.1 (rounding upwards)
// other cases = 0.5
down = y1 > y0 ? y1 > y2 ? 1 : 0.5 : y1 > y2 ? 0.5 : -0.1
dty = points[i].dty * down //min absichern
}
path.push(
'C', 
flatten(points[i - 1][back ? 'x1' : 'x2'] + old_dtx / 2, points[i - 1].y - old_dty / 2),
flatten(x1 - dtx / 2, points[i].y - dty / 2),
flatten(x1, points[i].y), 
'Q',
flatten(x1 + dtx, points[i].y + dty),
flatten(x2, points[i].y)
);
}
old_dtx = dtx, old_dty = dty
}
if (back) { 
points.reverse()
path.push('Z')
}
}
function generateArea(width, height) {
const
// tongue control points for first wave
firstPoints = randomPoints(width, height),
// tongue control points for second wave
secondPoints = randomPoints(width, height),
start = [],
mid = [],
end = []
// first keyframe
generatePath(firstPoints.from, start, false)
generatePath(secondPoints.from, start, true)
// second keyframe
generatePath(firstPoints.to, mid, false)
generatePath(secondPoints.from, mid, true)
// third keyframe
generatePath(firstPoints.to, end, false)
generatePath(secondPoints.to, end, true)

return [
start.join(' '), 
mid.join(' '), 
end.join(' ')
]
}
const rect = document.querySelector('svg').getBoundingClientRect()
const animate = document.querySelector('#gooAnimate')
const areas = generateArea(rect.width, rect.height)
animate.setAttribute('values', areas.join(';'))
animate.beginElement() // trigger animation start
body {
position: relative;
margin: 0;
}
#content {
position: relative;
box-sizing: border-box;
background: #faa;
width: 100vw;
height: 100vh;
padding: 1em;
}
svg {
position: absolute;
width: 100%;
height: 100%;
top: 0%;
pointer-events: none;
}
#veil {
fill: skyblue;
}
#goo {
fill: #5b534a;
}
<div id="content">
<h1>Lorem ipsum dolor sit amet</h1>
<p>Lorem ipsum dolor sit amet, <a href="">consectetur</a> adipiscing elit. Nam eu sodales lectus. Sed non erat accumsan, placerat purus quis, sodales mi. Suspendisse potenti. Sed eu viverra odio. </p>
</div>
<svg xmlns="http://www.w3.org/2000/svg">
<rect id="veil" width="100%" height="100%">
<!-- background animation start time is relative to goo animation start time -->
<set attributeName="display" to="none" begin="gooAnimate.begin+3s" fill="freeze" />
</rect>
<path id="goo" d="" >
<animate id="gooAnimate" attributeName="d"
begin="indefinite" dur="6s" fill="freeze" keyTimes="0;0.5;1" />
</path>
</svg>

最新更新