3 回答

TA貢獻1765條經驗 獲得超5個贊
我很確定這不是最佳變體,但它似乎可行,至少在 Firefox 中是這樣。Chrome 在動畫每個部分的初始幀方面存在一些問題。
我稍微重寫了 gooey 過濾器代碼以提高可讀性,同時保持相同的效果。
只有
.goo-one
和子 div 獲得背景顏色。這使得.goo-two
變得透明成為可能。這兩個部分有不同的過濾器,但過濾器區域垂直增加,以便在過渡開始時到達屏幕底部。
第一個濾鏡使用天藍色作為背景填充。
第二個過濾器有一個棕色填充,但它的應用是相反的:它只顯示在 goo 區域之外,而內部區域是空的。構成 goo 區域的 div 矩形不跨越整個
.gooTwo
.?為了也填充(并在倒置后為空)頂部,<div class="first">
需要額外的部分。在第二個 goo 部分的過渡開始時,過濾器區域上限設置在屏幕下邊界下方。這隱藏了天藍色背景,同時第二個粘性部分變得可見。
請注意,為了更好的瀏覽器兼容性,元素的 CSS 略有變化
svg
。作為概念證明,在容器 div 中添加了一些內容。表明
pointer-event: none
需要a;否則無法與頁面進行交互。
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>

TA貢獻2036條經驗 獲得超8個贊
首先,我將開始使用一個 div 和多個漸變來構建形狀。
這是一個使用我們可以輕松定位的不固定漸變(相同寬度和不同高度)的想法:
Hide code snippet
: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 來生成所有這些寬度:
Hide code snippet
: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 我們可以有我們的第一個動畫:
Hide code snippet
: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>
展開片段
仍然不完美,但我們也可以添加一些漸變動畫來調整大?。?/p>
Hide code snippet
: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 來生成代碼)
對于第二個動畫,我們將做同樣的事情,但我們認為遮罩屬性中的漸變層具有相反的效果(漸變層將被移除以查看剩余部分)
Hide code snippet
: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>
展開片段
我們做了一些代碼優化,只保留一個元素:
Hide code snippet
: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
更新
不使用面具的另一個想法。訣竅是使漸變居中。此解決方案將有更多支持,但底部和頂部形狀都是對稱的
Hide code snippet
: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

TA貢獻1859條經驗 獲得超6個贊
這是避免所有過濾器、掩蔽和組合困難的嘗試。它只是一些 bezier 路徑的SMIL 動畫,應該支持,沒有任何錯誤。我還沒有找到第一波和第二波同時出現在屏幕上的解決方案。
我承認最費力的部分是為路徑設計算法,其他一切都相對簡單。
“goo”是跨客戶區移動的具有上下邊界的區域,同時路徑的形式發生變化。我試圖在代碼注釋中描述哪些部分可以調整。路徑組合的基本結構確保了一個重要的限制:作為一個整體的路徑對于動畫的不同關鍵幀不能有不同的路徑命令序列,否則平滑的動畫將失敗。換個號碼應該沒問題。
在 goo 的后面是一個不透明的矩形,它最初隱藏了內容。當 goo 在屏幕上運行時,它會在適當的時間隱藏。
動畫的時間在<set>
和<animate>
元素的屬性中定義。請注意 goo 動畫運行了 6 秒,而背景矩形的隱藏發生在 3 秒后。此分布匹配屬性的值<animate keyTimes>
:0;0.5;1
,您可以將其讀作 0%、50%、100% 作為關鍵幀的計時。觸發器必須與中間關鍵幀匹配的時間<set>
,因為那是 goo 覆蓋整個客戶區域的時間。
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>
添加回答
舉報