Naughty Button

Inspired by an exsiting project based on Vue.js. I implemented this playful button with vanilla JavaScript.

The idea is to disable the submit button unless all fields (username and password) are both filled. And when it’s in the “disabled” state, mouseenter event will make it move instead of submit.

Create a varialbe isReadyToSubmit to keep the shared state and add three event listeners on input fields and the submit button.

// Event Listeners
elements.form.addEventListener('input', handleFormInput);
elements.submitBtn.addEventListener('click', handleFormSubmit);
elements.submitBtn.addEventListener('mouseenter', moveButton);
isReadyToSubmit = enteredUsername && enteredPassword;
submitBtn.disabled = !isReadyToSubmit;

There are actually two buttons in the submit-box container. One stays in place while the other moves around when the username or password is not filled.

It’s a common practice to disable the submitt button if required fields are missing. The key difference here is animation. We want the button moves like a jellyfish, or a squeezed sponge.

Two settings needed to create the “escaping” effect.

  • Target location: where the button moves to if cursor touches it when username or password is empty
  • Animation

Target Position

The submit button moves to a random position basedo on the width and height of the button. So actually it moves around its original place.

Another option (like the original implementation) is to move the submit button toward to the edge and back to the orignal position when hitting the edge.

Once we get the x and y coordinates, set them to CSS custom properties so that the values can be consumed in the CSS file.

const basemovex = 84;
const baseemovey = 36;

const xMove = Math.random() * basemovex * (Math.random() < 0.5 ? -1 : 1)
const yMove = Math.random() * baseemovey * (Math.random() < 0.5 ? -1 : 1)
e.target.style.setProperty('--x-move', `${xMove}px`);
e.target.style.setProperty('--y-move', `${yMove}px`);
e.target.classList.add('moving');

Get the updated x and y coordinates in CSS

.submit-btn {
  background-color: #f4a261;
  color: #ffffff;
  border: none;
  cursor: pointer;
  position: absolute;
  left: 0;
  transition: all 0.6s;
  will-change: transform;
  transform: translate(var(--x-move, 0), var(--y-move, 0));
}

Animation

Use animation to simulate the “squeezing” effect. That what draws my attention in the first place.

When the “moving” criteria is met

  • missing username or password
  • mouse reaches submit button Add a CSS class ‘moving’ to the submit button.

Then trigger animation by adding the ‘moving’ class.

// e.target is submit button
e.target.classList.add('moving');

CSS custom properties naming rulee: two dashes as prefix

.submit-btn.moving {
  cursor: not-allowed;
  animation: jellyBounce .6s cubic-bezier(.04,.43,.025,1.07);
  transform: scale(1)
}

@keyframes jellyBounce {
  0%, 100% {
    transform: scale(1) translate(var(--x-move), var(--y-move));
  }
  50% {
    transform: scale(1.2, .8) translate(var(--x-move), var(--y-move));
  }
  80% {
    transform: scale(.9, 1.1) translate(var(--x-move), var(--y-move));
  }
}

If going for the moving to the edge option, IntersectionObserver can be used to monitor submit button. When the button reaches or get close to the edge, reset its position to the original.

const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach((entry) => {
      // If button is not fully visible and form isn't ready, reset position
      if (!entry.isIntersecting && !isReadyToSubmit) {
        submitBtn.style.setProperty('--x-move', '0px');
        submitBtn.style.setProperty('--y-move', '0px');
        submitBtn.classList.remove('moving');
      }
    });
  },
  {
    root: document.querySelector('.form-container'),
    // Reset when less than 80% of button is visible
    threshold: 0.8,
  }
);

observer.observe(submitBtn);