Click on the feedback

Have you ever noticed that some app buttons, links and interactive cards feel like they are clicked on, while others feel like they are clicked on blank paper? What makes them so obvious? .

The mouse moved to the hands, the mouse to click the button press up animation, touch-screen application click on the screen when vibration, the effect is to give the user a is my behavior produces the effect of intuition, these effects have also been referred to as click feedback, it may seem application details, but as long as a little into a little mind, The improved user experience is very obvious

Water wave effect

Here is one of my favorite click-feedback effects. When the user clicks, a ripple effect of water wave diffusion will be produced with the click center as the center. It is suitable for various scenes, beautiful and not grandiose, and the key is to bring intuitive feedback to the user.

To see implementation

First of all, Vue3 custom instruction is used for encapsulation. Compared with Vue2, the custom instruction of Vue3 has little change. Please refer to Vue3 custom instruction for details. Our goal is to complete a basic prototype of the water wave command, which is developed step by step.

Customize a default water ripple style

Water ripple is actually through a user click on the location of the generated a small circle, and gradually expand to the whole clicked element size a process, so here to make a basic style of water waves, and set up good animation, excessive animation should be a process of a slow start, quick, using Bessel curve custom here, Not sure how to debug animation curve can see this article

.my-ripple {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 100;
  border-radius: 50%;
  background-color: currentColor;
  opacity: 0;
  transition: transform 0.2 s cubic-bezier(0.68.0.01.0.62.0.6), opacity 0.08 s linear;
  will-change: transform, opacity;
  pointer-events: none;
}
Copy the code

Calculate the position and diameter of water ripples

We can render the wave using transition from its diameter to the point where the user clicked on it, to the point where the wave was created (x,y) and the point where the transition ended. Our elements are all rectangles. No matter the user clicks from any coordinate of the element, the circle with the hypotenuse as its diameter can perfectly cover the whole element. For the calculation of the hypotenuse, we use elementary school mathematics knowledge to find the sum of squares of both sides and take the square root.

First arrow: expected water wave second arrow: water wave created at element (0,0) third arrow: water wave created at element (0,0) without rounded corner effect

We can find that the wave created by element (0,0) can be offset to get the wave we want, from which we can infer

The size of the water wave at the end of the animation = (x,y) when the hypotenuse of the circle was created = the position where the user clicked

function computeRippleStyles(element, event) {
  const { top, left } = element.getBoundingClientRect()
  const { clientWidth, clientHeight } = element
  
  const radius = Math.sqrt(clientWidth ** 2 + clientHeight ** 2) / 2
  const size = radius * 2
  
  const localX = event.clientX - left
  const localY = event.clientY - top

  const centerX = (clientWidth - radius * 2) / 2
  const centerY = (clientHeight - radius * 2) / 2

  const x = localX - radius
  const y = localY - radius

  return { x, y, centerX, centerY, size }
}
Copy the code

Create water wave when mouse down

Then we need to create the water wave when the mouse is pressed down, and listen to the event of the mouse being pressed down. Here we take PC as an example. When the water wave is just created, transform is used to reduce it to 0.3, which is the relatively appropriate size that the author tried to create, and then modify transform to trigger the animation of excessive water wave diffusion. It makes the ripples more textured.

function createRipple(event) {
  const container = this
  const { x, y, centerX, centerY, size } = computeRippleStyles(container, event)
  const ripple = document.createElement('div')
  ripple.classList.add('my-ripple')
  ripple.style.opacity = ` 0 `
  ripple.style.transform = `translate(${x}px, ${y}px) scale3d(.3, .3, .3)`
  ripple.style.width = `${size}px`
  ripple.style.height = `${size}px`
  // Record the wave creation time
  ripple.dataset.createdAt = String(performance.now())

  const { position } = window.getComputedStyle(container)
  container.style.overflow = 'hidden'
  position === 'static' && (this.style.position = 'relative')

  container.appendChild(ripple)

  window.setTimeout(() = > {
    ripple.style.transform = `translate(${centerX}px, ${centerY}px) scale3d(1, 1, 1)`
    ripple.style.opacity = `. 25 `})}const VRipple = {
  mounted(el) {
    el.addEventListener('mousedown', createRipple)
  }
}
Copy the code

Destroy water wave when mouse up

When the mouse is lifted, you just need to find the generated water wave node to modify the transparency, and then remove the water wave node after the transparency modification animation is finished

function removeRipple() {
  const container = this
  const ripples = container.querySelectorAll('.my-ripple')
  if(! ripples.length) {return
  }

  const lastRipple = ripples[ripples.length - 1]
  // Calculate how long the diffusion animation will take from the wave creation time to ensure that each wave has fully performed the diffusion animation
  const delay = 300 - performance.now() + Number(lastRipple.dataset.createdAt)

  setTimeout(() = > {
    lastRipple.style.opacity = ` 0 `
    
    setTimeout(() = >lastRipple.parentNode? .removeChild(lastRipple),300)
  }, delay)
}

const VRipple = {
  mounted(el) {
    el.addEventListener('mousedown', createRipple)
    document.addEventListener('mouseup', removeRipple)
  },
  unmounted(el) {
    el.removeEventListener('mousedown', createRipple)
    document.removeEventListener('mouseup', removeRipple)
  }
}
Copy the code

Extend your wave options with the directive Binding

You can also extend your directive with binding, providing options to change colors, disable states, etc., but I won’t go into details here. Let’s take a look at the results.

Write in the last

At this point we have implemented a simple Ripple directive, which is also available in our component library, so a more complete version can be found in our source code. First of all, we would like to thank the Nuggets community, some of you have started to pr some code into our warehouse, we are also happy to do such a thing with the community partners, in addition, our component library team has been collecting fans to participate in the contribution, interested friends welcome to join the discussion, The way to join is to directly go to the warehouse to raise the issue and leave the email, we will deal with it in the first time, we hope to star for us if you are interested, pay attention to us, the support and interest of community partners is our biggest motivation.

Warehouse address Document address