DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Subtitles: The Good, the Bad, and the Resource-Heavy
  • Loader Animations Using Anime.js
  • Next.js Theming: CSS Variables for Adaptive Data Visualization
  • Creating Scrolling Text With HTML, CSS, and JavaScript

Trending

  • Code Reviews: Building an AI-Powered GitHub Integration
  • Agile’s Quarter-Century Crisis
  • How to Merge HTML Documents in Java
  • Developers Beware: Slopsquatting and Vibe Coding Can Increase Risk of AI-Powered Attacks
  1. DZone
  2. Coding
  3. Languages
  4. Creating 3D CSS Buttons That Move as You Mouse Over

Creating 3D CSS Buttons That Move as You Mouse Over

Let's take a look at how to make 3D Javascript/CSS buttons with shadows, edges, and animations that move as you hover your mouse over them.

By 
Johnny Simpson user avatar
Johnny Simpson
DZone Core CORE ·
Oct. 13, 21 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
6.4K Views

Join the DZone community and get the full member experience.

Join For Free
3D buttons

Recently I was toying with the idea of a 3D button that moves as the user moves their mouse around it. To further this effect, I added some 3D shadows which move in tandem to give the illusion of a 3D button that is sitting off the page and moves with the user’s mouse movements.

Demo

How Does It Work?

The fundamental concept behind these buttons is that we need to track when the user mouses over the button, moves, and mouses out. On mouseover, we will move the button so it appears 3D. On mouse out, we will reset it.

Before we get to the Javascript, let’s make our button look good. Our HTML will look like this:

HTML
 
<button class="button"><span>Hover!</span></button>


And our CSS looks like this:

CSS
 
button {
    box-shadow: none;
    background: transparent;
    transform-style: preserve-3d;
    padding: 0;
    height: auto;
    float: none;
}

button span {
    background: linear-gradient(180deg, #ff7147, #e0417f);
    font-size: 2rem;
    padding: 1rem 2rem;
    line-height: 3rem;
    will-change: transform, filter;
    float: none;
    margin: 0;
    transition: all 0.15s ease-out;
    height: auto;
    border-radius: 100px;
    overflow: hidden;
    display: block;
    margin: 0px auto;
    display: block;
    transform: rotateX(0deg) rotateY(0deg) scale(1);
    filter: drop-shadow(0 15px 15px rgba(0,0,0,0.3));
    font-weight: 600;
    perspective-origin: 0 0;
    letter-spacing: 0;
}


Minor Background Animations

You may have noticed that the third button has a background animation. If you’re interested in how I did that, I used a pseudo-element that is moving via animation. The pseudo-element has a simple gradient, and the overflow is hidden. You can test this out yourself by removing overflow: hidden from the span and button elements.

How the Javascript Works

Let’s take a look at our Javascript now. You may have noted we have two elements for our button:  the button itself and a span inside of it. There is a good reason for this  —  this lets us apply 3D perspective on the parent, which is required for the effect to work. It also allows us to target the parent for the hover effect. If we use hover on the child, the effect will bug out as the child will rotate, and we will miss the hitbox.

I am using a function that uses the event variable (e) and references both the span (noted here as item) and the button (referenced as parent).

JavaScript
 
let calculateAngle = function(e, item, parent) {
    let dropShadowColor = `rgba(0, 0, 0, 0.3)`
    // If the button has a data-filter-color attribute, then use this for the shadow's color
    if(parent.getAttribute('data-filter-color') !== null) {
        dropShadowColor = parent.getAttribute('data-filter-color');
    }

    // If the button has a data-custom-perspective attribute, then use this as the perspective.
    if(parent.getAttribute('data-custom-perspective') !== null) {
        parent.style.perspective = `${parent.getAttribute('data-custom-perspective')}`
    }

    // Get the x position of the users mouse, relative to the button itself
    let x = Math.abs(item.getBoundingClientRect().x - e.clientX);
    // Get the y position relative to the button
    let y = Math.abs(item.getBoundingClientRect().y - e.clientY);

    // Calculate half the width and height
    let halfWidth  = item.getBoundingClientRect().width / 2;
    let halfHeight = item.getBoundingClientRect().height / 2;

    // Use this to create an angle. I have divided by 6 and 4 respectively so the effect looks good.
    // Changing these numbers will change the depth of the effect.
    let calcAngleX = (x - halfWidth) / 6;
    let calcAngleY = (y - halfHeight) / 4;

    // Set the items transform CSS property
    item.style.transform = `rotateY(${calcAngleX}deg) rotateX(${calcAngleY}deg) scale(1.15)`;
        
    // And set its container's perspective.
    parent.style.perspective = `${halfWidth * 2}px`
    item.style.perspective = `${halfWidth * 3}px`

    // Reapply this to the shadow, with different dividers
    let calcShadowX = (x - halfWidth) / 3;
    let calcShadowY = (y - halfHeight) / 3;
        
    // Add a filter shadow - this is more performant to animate than a regular box shadow.
    item.style.filter = `drop-shadow(${-calcShadowX}px ${calcShadowY}px 15px ${dropShadowColor})`;
}


Effectively this splits the button into four quadrants. The midpoint represents an angle of change on the X and Y axis of 0, while a movement to the left results in a more negative Y angle, and a more positive one to the right. The same applies for X, where moving the cursor up turns the X angle more positive, and down, more negative.

Some things worth noting:

  • We are using filter box-shadows  because they transition better with CSS’ transition property.
  • I’ve added the ability to add custom perspective and box-shadow colors   to give more flexibility without having to change the code.
  • The effect is modulated by dividing the calcAngle* variables. If you change how much you divide them by, or even change the perspective, the effect will become more or less pronounced.

Applying Our Function to Each Button

To apply our function to each button, we simply iterate through them all with forEach. 

JavaScript
 
document.querySelectorAll('.button').forEach(function(item) {
    // Add on mouseenter
    item.addEventListener('mouseenter', function(e) {
        calculateAngle(e, this.querySelector('span'), this);
    });
    // Add on mousemove
    item.addEventListener('mousemove', function(e) {
        calculateAngle(e, this.querySelector('span'), this);
    });

    // Reset everything on mouse leave
    item.addEventListener('mouseleave', function(e) {
        let dropShadowColor = `rgba(0, 0, 0, 0.3)`
        if(item.getAttribute('data-filter-color') !== null) {
            dropShadowColor = item.getAttribute('data-filter-color')
        }
        item.querySelector('span').style.transform = `rotateY(0deg) rotateX(0deg) scale(1)`;
        item.querySelector('span').style.filter = `drop-shadow(0 10px 15px ${dropShadowColor})`;
    });
})


We’re Done

With that, we have recreated the effect shown at the start of the article. We hope you’ve enjoyed this guide.

CSS

Published at DZone with permission of Johnny Simpson, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Subtitles: The Good, the Bad, and the Resource-Heavy
  • Loader Animations Using Anime.js
  • Next.js Theming: CSS Variables for Adaptive Data Visualization
  • Creating Scrolling Text With HTML, CSS, and JavaScript

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!