Creating Seamless, Unified Shape-to-Shape (Containers) Transition with CSS and JavaScript - A Step-by-Step Guide

I am experimenting with creating a unique effect where two rectangular shapes, each containing text and with rounded ends, move towards each other, merge to form a single rounded rectangle as the page is scrolled down, and then separate back when scrolling up. I want these shapes to remain fixed in their position relative to the viewport during scrolling. https://i.sstatic.net/XIk6RUdc.gif

My Attempts So Far:

  • I have tried using CSS translations and transformations along with JS adjustments of the border-radius properties.
  • I experimented with clip-path, but it resulted in ellipse shapes instead.
  • Using HTML Shapes did not work as they need to be containers for my content.

My closest success has been getting the shapes to move close together and slightly overlap. However, other attempts have led to ellipses, unnatural border-radius behavior, or lack of cohesion between the elements.

document.addEventListener('scroll', () => {
    const scrollPercent = window.scrollY / (document.body.scrollHeight - window.innerHeight);
   
    const movement = (window.innerWidth / 2 - 115) * scrollPercent;

    const shape1 = document.querySelector('.shape:nth-child(1)');
    const shape2 = document.querySelector('.shape:nth-child(2)');
    shape1.style.transform = `translateX(${movement}px)`;
    shape2.style.transform = `translateX(-${movement}px)`;
});
body {
    width: 90%;
    margin: 0 auto;
    padding: 20px;
    height: 2000px;
}

.container {
    position: sticky;
    top: 20px;
    display: flex;
    justify-content: space-between;
}

.shape {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100px;
    height: 30px;
    border-radius: 15px;
    background-color: #000;
    color: #fff;
    transition: transform 0.3s ease-out;
    transform: translateX(0%);
}
<body>
    <div class="container">
        <div class="shape" id="shape1">Shape 1</div>
        <div class="shape" id="shape2">Shape 2</div>
    </div>
</body>

Question: I suspect that I may need to explore clip-path further to achieve my desired effect. How can I refine my CSS and adjust my JavaScript to create the morphing effect depicted in the image above while maintaining rounded edges and ensuring coherence between the two container elements? Any suggestions or corrections regarding my current approach would be greatly appreciated. Thank you.

Answer №1

One effective technique involves using an SVG filter:

<svg class="morph-filter" viewbox="0 0 0 0">
  <filter id="morph">
    <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 64 -32" result="morph" />
    <feBlend in="SourceGraphic" in2="morph" />
  </filter>
</svg>
.container {
  filter: url(#morph);
}

document.addEventListener('scroll', () => {
  const scrollPercent = window.scrollY / (document.body.scrollHeight - window.innerHeight);

  const movement = (window.innerWidth / 2 - 115) * scrollPercent;

  const shape1 = document.querySelector('.shape:nth-child(1)');
  const shape2 = document.querySelector('.shape:nth-child(2)');
  shape1.style.transform = `translateX(${movement}px)`;
  shape2.style.transform = `translateX(-${movement}px)`;
});
body {
  width: 90%;
  margin: 0 auto;
  padding: 20px;
  height: 2000px;
}

.container {
  position: sticky;
  top: 20px;
  display: flex;
  justify-content: space-between;
  filter: url(#morph);
}

.shape {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100px;
  height: 30px;
  border-radius: 15px;
  background-color: #000;
  color: #fff;
  transition: transform 0.3s ease-out;
  transform: translateX(0%);
}
<body>
  <div class="container">
    <div class="shape" id="shape1">Shape 1</div>
    <div class="shape" id="shape2">Shape 2</div>
  </div>

  <svg class="morph-filter" viewbox="0 0 0 0">
    <filter id="morph">
      <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 64 -32" result="morph" />
      <feBlend in="SourceGraphic" in2="morph" />
    </filter>
  </svg>
</body>

Breaking it Down

The process begins with <feGaussianBlur /> to blur the two shapes. Similar to applying filter: blur(10px), this step creates a blurred version of the source image, slightly larger than its original size, and saves the output in the blur channel.

<feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur" />

Next, using <feColorMatrix />, only the alpha channel of the image is "sharpened" without affecting the RGB channels. By adjusting the alpha channel with significant values (64 -32), pixels with alpha values less than 0.5 become transparent while those above 0.5 stay opaque (alpha values are clamped between 0 and 1). The resulting image is stored in the morph channel.

<feColorMatrix in="blur" mode="matrix" values="
  1 0 0 0 0
  0 1 0 0 0
  0 0 1 0 0
  0 0 0 64 -32" result="morph" />

Finally, <feBlend /> combines these channels so that when the two shapes nearly touch, areas where they overlap with blurry edges will have alpha values greater than 0.5, creating the illusion of morphing between the shapes.

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

Retrieve a targeted data value from a JSON object based on an array value

Looking at the JSON array and another array provided below. JSON OBJECT { id: 1, name: 'abc', email: '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="dfbebdbc9fb8b2beb6b3f1bcb0b2">[emai ...

Change the opacity of a DIV or any other element when hovering over it

I have successfully applied opacity: 1; to a paragraph when hovering over itself. However, I want the opacity to also change when hovering over the header above it. Here is my CSS code: .testH { font-family: impact; text-align: center; font-s ...

Calculating the difference between the old scrolltop and the new scrolltop in jQuery/Javascript

I have a seemingly straightforward question, yet I am struggling to find a solution. Each time a user scrolls, the scrollTop value changes. I want to subtract the previous value from the new value of the scrollTop. However, I am unsure how to store the old ...

What are the steps to extracting JSON data in Node.js?

I am currently utilizing the API's node wrapper provided by MySportsFeeds. You can find more information about it here: https://github.com/MySportsFeeds/mysportsfeeds-node/blob/master/README.md The API call is functioning smoothly and the data is aut ...

Manipulating classes within ng-class in AngularChanging classes in ng-class dynamically

Featuring multiple elements with an ng-class that behaves similarly to a ternary operator: ng-class="$ctrl.something ? 'fa-minus' : 'fa-plus'" To access these elements, we can compile all the ones with fa-minus and store them in a lis ...

What is the method for passing an element in Angular2 Typescript binding?

Is there a way to retrieve the specific HTML dom element passed through a binding in Angular? I'm having trouble figuring it out, so here is the relevant code snippet: donut-chart.html <div class="donut-chart" (donut)="$element"> ...

Is it possible to dynamically set the maximumSelectionLength in a select2 dropdown?

In my HTML, I have two select elements. One is for multiple selection of items in a dropdown and the other is for adding tags. If the number of selected items is 3, then users should only be allowed to add 3 tags - no more and no less. $(".js-example-b ...

Error: Attempting to access properties of an undefined variable (specifically 'document') is not allowed

The other day, while working on a project, I encountered an issue with my GraphQL implementation using the JavaScript graphql-request library. Here is the snippet of code that caused the error: import { request, gql } from 'graphql-request' const ...

What is the best way to save JavaScript array information in a database?

Currently working on developing an ecommerce website. My goal is to have two select tags, where the options in the second tag are dynamically populated based on the selection made in the first tag. For example, if "Electronics" is chosen in the first tag f ...

Is it necessary to call detach() in rangy?

Utilizing the rangy library in my project and reviewing the documentation for detach: "Destroys the range when it is no longer to be used." I am encountering a dilemma as there isn't a suitable location for me to invoke detach in my code for certain ...

What is the best way to display JQuery mobile tap event information in real-time?

I am experiencing an issue in my code where the tap event is being triggered, but the array[i] value is printed as null. Whenever I click on any index, it always prints an empty string (" "). Why does it display null instead of the clicked value? I am see ...

Tips for incorporating unique images into each individual flex box

I am new to the world of coding, having just embarked on this journey less than a week ago. Following a tutorial by a developer on YouTube, I tried to add my own twist to the project in order to differentiate it from the original. However, I've hit a ...

Exploring the Worldwide Influence of TypeScript, React, and Material-UI

I am currently following an official tutorial on creating a global theme for my app. In my root component, I am setting up the global theme like this: const themeInstance = { backgroundColor: 'cadetblue' } render ( <ThemeProvider theme ...

Experiencing SyntaxError when utilizing rewire and mocha for Node.js testing. Unexpected token encountered

Trying to test a function exported from a nodejs file, I am utilizing q to manage promises. The function returns a promise that is either resolved or rejected internally through a callback. Within this callback, another function from a different location i ...

Bootstrap's nested grid system allows for intricate and dynamic layouts within

I am looking to design a grid with two columns, where on small and larger screens the width of the right column adjusts itself to accommodate its content, while the left column takes up the remaining space. On extra small screens, the layout changes to two ...

Getting files onto your device - the Phonegap way

I'm having trouble exporting or downloading information to a file. It works fine in my browser, but when I try it in my phonegap app, the file just opens as text without an option to save it or return to the app. Any advice? Keep in mind that I'm ...

How can we modify the elements within an Object tag in HTML? If it is achievable, what is the process?

I am working on a project involving an HTML page that includes content from an external website using an HTML object tag as shown below: <object data="someUrl-on-different-domain-than-host" type="text/html"></object> My goal is to use jQuery ...

Utilizing VueJS to effectively integrate the Google Places API with multiple references

I am currently integrating the Google Places API into my project and have encountered an interesting challenge. I need to implement multiple delivery addresses, requiring me to modify how the Google Places API functions. Below is my existing code snippet: ...

Creating a personalized v-for loop with v-if to apply a specific class to a div element

How can I correctly utilize v-if when using v-for inside? I am aiming to implement a condition where if the index is 0 or it's the first data, then add an active class. <div class="item active" v-for="(item, key, index) in slideItem" :key="item._ ...

Why is this element occupying an excessive amount of space horizontally?

I've been diligently working on a unique custom WordPress theme as part of a school project. Our task is to redesign the homepage of an association, in my case, a theater group. One of the challenges I encountered was customizing the WordPress menu t ...