Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Clipping" Background to See Below Itself in Stacking Context

[ Note: Looking for a cross-browser solution that does not flash the body's background momentarily between each wave of goo as seen in ccprog's answer; Ideally, the solution should not involve waiting until the end of the first wave to begin displaying the second wave, so that both waves can run concurrently. I am willing to forego dynamically randomized goop in order for an ideal solution. ]

Does anybody know how I can make the second wave of orange goo (.goo-two) "cut through" the first wave of brown goo (.goo-one) and the skyblue container (.goo-container) to show or expose the red body element (body) or, for that matter, any other element below it in the stacking context? Is it possible?

Notably, the reason I have given the container (.goo-container) a solid background is because I was using this to cover up the loading process of the rest of the website, whereby I was hoping the orange goo (.goo-two) can be used to reveal the content. It gets even trickier because the orange goo starts dripping before the brown goo finishes, which would be the perfect time to change the background of the contianer (.goo-container) from skyblue to transparent, although a semi-transparent gradient as the background can likely be used to still achieve this. (Either that or something altogether different like duplicating the orange layer and use one to clip the brown path and the other to clip skyblue layer.)

Any ideas?

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>
like image 624
oldboy Avatar asked Nov 23 '20 21:11

oldboy


People also ask

What is background clipping?

background-clip lets you control how far a background image or color extends beyond an element's padding or content.

What does background-clip do in CSS?

The background-clip CSS property sets whether an element's background extends underneath its border box, padding box, or content box.

How do you determine the background area of a painting?

The CSS background-clip property specifies the painting area of the background. The property takes three different values: border-box - (default) the background is painted to the outside edge of the border. padding-box - the background is painted to the outside edge of the padding.


1 Answers

I am pretty sure this is not the optimal variant, but it seems to work out, at least in Firefox. Chrome has some issues with the initial frames of each part of the animation.

  • I have rewritten the gooey filter code a bit to improve readability while maintaining the same effect. For an explanation, see my article.
  • Only .goo-one and the child divs get a background color. This makes it possible for .goo-two to become transparent.
  • The two parts get different filters, but the filter region is increased vertically for both to reach the bottom of the screen at the start of the transition.
  • The first filter has the skyblue color as a background fill.
  • The second filter has a brown fill, but its application is inverted: it is shown only outside the goo areas, leaving the inside area empty. The div rectangles making up the goo area do not span the entire .gooTwo. To also fill (and after inversion, empty) the top part, an extra <div class="first"> is needed.
  • At the start of the transition for the second goo part, the upper filter region limit is set below the lower screen boundary. This hides the skyblue background at the same time the second goo part becomes visible.
  • Note the slight change in the CSS for the svg element for better browser compatibility.
  • Just as a proof of concept, some content is added inside the container div. It shows that a pointer-event: none is needed; otherwise no interaction with the page would be possible.

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>
like image 182
ccprog Avatar answered Oct 22 '22 12:10

ccprog