Revealing the Background by Clipping Below It in a Stacking Context

[ Note: Seeking a solution that works across all browsers without causing the body's background to flash between each wave of goo; Ideally, the solution should allow both waves to run concurrently without delay. I am willing to sacrifice dynamic randomness in the goop for an optimal solution. ]

Is there a way to 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 reveal the red body element (body) or any other element beneath it in the stacking context? Is this even possible?

I have given the container (.goo-container) a solid background to hide the loading process of the website content, with the intention of using the orange goo (.goo-two) to show the content. However, a challenge arises as the orange goo begins dripping before the brown goo finishes, creating an opportunity to change the background of the container from skyblue to transparent, possibly using a semi-transparent gradient. Alternatively, a different approach like duplicating the orange layer could be considered - using one to cut through the brown path and the other to cut through the skyblue layer.

Any suggestions or thoughts on how to achieve this effect?

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>

Answer №1

It's possible that this may not be the most optimal solution, but it appears to function well, especially in Firefox. Chrome does encounter some issues with the initial frames of each segment of the animation.

  • I made some adjustments to the gooey filter code for better readability while preserving the same effect. You can find a detailed explanation in my article.
  • Only .goo-one and its child divs are assigned a background color. This allows for .goo-two to remain transparent.
  • Each part utilizes distinct filters, but their filter areas extend vertically to reach the bottom of the screen at the beginning of the transition.
  • The first filter features a skyblue background fill.
  • For the second filter, a brown fill is used, but it operates inversely: it only appears outside the goo areas, leaving the interior area empty. The div rectangles forming the goo area do not cover the entire .gooTwo. To also fill (and subsequently empty after inversion) the top portion, an additional
    <div class="first">
    element is necessary.
  • At the onset of the transition for the second goo segment, the upper limit of the filter region is positioned below the lower boundary of the screen. This conceals the skyblue background while simultaneously revealing the second goo part.
  • Please note the slight adjustment in the CSS for the svg element to enhance compatibility across different browsers.
  • As a proof of concept, some content has been included within the container div. It demonstrates the necessity of using pointer-event: none; otherwise, interacting with the page would not be feasible.

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>

Answer №2

Let's begin by creating the shape using a single div and multiple gradients.

One approach is to use uniform gradients with different heights that can be easily positioned:

: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>

We can also introduce variable width gradients where JavaScript will be necessary to generate them:

: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%;
  background-repeat:no-repeat;
  filter: url('#goo-filter');
}
<div class='goo-container'>
</div>;



<>

By applying additional CSS, we can create our first animation:

: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;
  ...


</div>;
<svg ...>
  <defs>...

Although not perfect, we can enhance it further by adding gradient animations to adjust sizes dynamically:

...

This complex solution may require JS or SASS for code generation based on previous elements' sizes.


For the second animation, we'll utilize mask layers instead of masks, resulting in a symmetrical effect with better support:

... And here's a dynamic SASS-powered implementation: SASS Version

Answer №3

Attempting to overcome filter, masking, and composition complications, a SMIL animation using bezier paths has been created with the aim of smooth support without any glitches. So far, achieving simultaneous appearance of the first and second wave on the screen remains unresolved.

The algorithm for defining the path proved to be the most demanding task, while everything else fell into place quite straightforwardly.

The concept revolves around a moving "goo" area with upper and lower boundaries traversing the client space while the shape of the path evolves simultaneously. Adjustments can be made in specific code sections as indicated by comments. A critical rule is that the path structure must remain consistent across different keyframes; otherwise, the fluidity of the animation will be compromised. Altering numerical values presents no challenge.

Behind the goo, there lies an opaque rectangle initially obscuring the content. This rectangle is cleverly revealed at a strategic moment as the goo moves across the screen.

The timing aspects of the animation are outlined through attributes within the <set> and <animate> elements. Note that the goo animation lasts for 6 seconds, with the concealed background being unveiled after 3 seconds. This distribution aligns with the values set in the <animate keyTimes> attribute: 0;0.5;1, which correspond to 0%, 50%, and 100% timings for the keyframes. The trigger time for <set> must coincide with the middle keyframe when the goo covers the entire client area.

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++) {
    ...

// Rest of the JavaScript function definitions would follow here.

</code></pre>
<pre class="snippet-code-css lang-css"><code>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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Incorporating JSTree Into Your Website's Heading: A Step-By-Step

I'm in search of a way to integrate the following code snippet as a JSTree into the heading section of a webpage, depicted below: <div id="jstree"> <ul> <li>Core 1 <ul> & ...

Strange behavior observed when making HTTP requests while updating views during load method

I have an AngularJS application with multiple views. One of these views controls a tool that can be in three different states: started, stopped, or paused, each corresponding to a different button. The tool is located on a server, and I want the view to u ...

Encountering an Ajax issue while transmitting data through Internet Explorer 11 - received error message "SCRIPT65535 argument not optional"

I'm encountering an issue when attempting to send data using AJAX. While it functions correctly on most browsers, I am encountering an error specifically with IE 11. The function causing the problem is called addToCart. It requires several parameters ...

Modify content on a link using AngularJS

Currently, my setup looks like this: HTML Code <div ng-app="home" ng-controller="customersCtrl"> <div ng-bind-html="home" id="content">{{content}}</div> </div> JS Code angular.module('home', []).controller('c ...

Having trouble activating the Samsung Tizen TV through the designated APIs

I currently have a Tizen Application for managing TV Operations such as Volume Controls and Power On/Off. I have implemented the use of b2bapis for Power Off functionalities, but I am struggling to locate comprehensive documentation on the same. The code s ...

Querying MongoDB using aggregation to find documents with a specific date range difference

I have been working on setting up a cron job to synchronize my documents. The goal is for the job to attempt syncing a specified number of times, but only after waiting at least 2 hours since the last attempt. Each document has a "lastSyncAt" field that I ...

When to safely store state data in React applications?

Imagine having a React component that updates its state based on form input. class Form extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = { someCheckboxState: fa ...

An unexpected error occurred while attempting to retrieve data from Firebase and display it in another component

An error occurred: Unhandled Runtime Error Error: Element type is invalid - expected a string (for built-in components) or a class/function (for composite components) but got undefined. This could be due to forgetting to export your component from the defi ...

Exploring Gatsby StaticQuery for Displaying HTML on the Page

I'm having difficulty understanding the behavior of <StaticContent>. If I turn off javascript in my application, anything enclosed within a <StaticContent> tag does not appear on the screen. Is this how it is supposed to work? Is there a ...

Utilizing JavaScript to sift through JSON data

Looking to efficiently parse a large JSON file based on its values. This is an excerpt from my extensive JSON code (with over 1000 entries). var jsonObject = [ { "UserId":10259, "FullName":"hello world", "CustomerId":"10165" ...

Align both text and images in the middle using HTML/CSS

I attempted to align an image next to text and center everything, but I'm facing some challenges. The arrow indicates where I would prefer the image to be placed. HTML <!DOCTYPE html> <head> <title>Simple Page</title> ...

What is the best way to adjust the size of an HTML form submit button

I have been working on a simple HTML form that I styled with CSS. I attempted to adjust the size of the submit button using the height attribute, but for some unknown reasons, it just wouldn't cooperate. However, when I tried using the width attribute ...

Issues with locating the fonts: React-slick paired with nextjs

Incorporating react-slick into a NextJs project has been quite seamless, however, I'm encountering issues with importing the necessary fonts from the CSS files. To address this snag, I've attempted the following steps: npm install slick-carouse ...

What is the best way to utilize bilinear color interpolation with JavaScript?

I'm grappling with the concept of bilinear interpolation, wondering if there's a more efficient method than what I've attempted below using the Culori library's interpolate() function. My uncertainty lies in whether it's correct t ...

What is the way to bypass certificate validation when making a Fetch API request in the browser?

The code snippet below is currently being executed in the browser: const response = await fetch(`${url}`, { method: 'POST', headers: { Authorization: `Basic ${authorization}`, }, body: loginData, }) Upon calling this co ...

Utilizing PHP for XML exportation and fetching it through AJAX to integrate it into the DOM, unfortunately, the XML content remains invisible

I've encountered a strange issue with a PHP script that generates valid XML output. I'm trying to fetch this data using an Ajax XMLHttpRequest call in the browser. Although Firebug confirms that the Ajax request is successful and the XML is vali ...

Exploring ways to verify various types of information in a Postman response

After creating test scripts with the following response data, { "page": 2, "per_page": 6, "total": 12, "total_pages": 2, "data": [ {"id": 7, &quo ...

Steps to activate an event when Windows is loaded

Every time windows load, I want to run $('select[name="order_id"]').change(), but it's not working as expected. After debugging in the browser console, I can see that the script $('select[name="order_id"]').cha ...

JavaScript Custom Asynchronous function not functioning as expected

console.log('Begin'); const executeAsync = async () => { for(let i=0; i<10000000000000; i++){} console.log('After loop'); } executeAsync(); console.log('finish'); I am looking for asynchronous behavior using async/a ...

compatibility of javascript with firefox

In my table, I have some JavaScript code that is causing some issues. <td nowrap="" style="background: none repeat scroll 0% 0% rgb(211, 211, 211);" onmouseout="this.style.background='#d3d3d3'; menu3.style.color='#000000'" onmouse ...