慕桂英3389331
首先,我将开始使用一个 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>展开片段仍然不完美,但我们也可以添加一些渐变动画来调整大小: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
慕丝7291255
这是避免所有过滤器、掩蔽和组合困难的尝试。它只是一些 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 startbody { 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>