Can anyone provide instructions on how to create a theme change animation using Vuetify?

Follow these steps to understand what I'm describing:

  1. Go to the Vuetify website
  2. Click on the settings icon
  3. Choose between the dark or light theme option.

When you switch between themes, there is an animation that occurs. A circle starts expanding from the button's position, eventually covering the whole screen in the new theme.

I couldn't find any specific instructions in the documentation on how to reproduce that effect. When I try, I see a transition where the colors slowly change to their new shades, but it doesn't look as polished as the website's animation.

Is this a built-in feature of Vuetify? How can I recreate this same behavior?

Answer №1

The functionality on vuetifyjs.com involves duplicating the entire DOM, adjusting scroll positions on the duplicate elements, and then applying a css clip-path transition to reveal the original DOM with the updated theme.

View the source here

watch(theme.global.name, themeTransition)

function themeTransition () {
  const x = performance.now()
  for (let i = 0; i++ < 1e7; i << 9 & 9 % 9 * 9 + 9);
  if (performance.now() - x > 10) return

  const el = document.querySelector('[data-v-app]')
  const children = el.querySelectorAll('*')

  children.forEach(el => {
    if (hasScrollbar(el)) {
      el.dataset.scrollX = String(el.scrollLeft)
      el.dataset.scrollY = String(el.scrollTop)
    }
  })

  const copy = el.cloneNode(true)
  copy.classList.add('app-copy')
  const rect = el.getBoundingClientRect()
  copy.style.top = rect.top + 'px'
  copy.style.left = rect.left + 'px'
  copy.style.width = rect.width + 'px'
  copy.style.height = rect.height + 'px'

  const targetEl = document.activeElement
  const targetRect = targetEl.getBoundingClientRect()
  const left = targetRect.left + targetRect.width / 2 + window.scrollX
  const top = targetRect.top + targetRect.height / 2 + window.scrollY
  el.style.setProperty('--clip-pos', `${left}px ${top}px`)
  el.style.removeProperty('--clip-size')

  nextTick(() => {
    el.classList.add('app-transition')
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        el.style.setProperty('--clip-size', Math.hypot(window.innerWidth, window.innerHeight) + 'px')
      })
    })
  })

  document.body.append(copy)

  copy.querySelectorAll('[data-scroll-x], [data-scroll-y]').forEach(el => {
    el.scrollLeft = +el.dataset.scrollX
    el.scrollTop = +el.dataset.scrollY
  })

  function onTransitionend (e) {
    if (e.target === e.currentTarget) {
      copy.remove()
      el.removeEventListener('transitionend', onTransitionend)
      el.removeEventListener('transitioncancel', onTransitionend)
      el.classList.remove('app-transition')
      el.style.removeProperty('--clip-size')
      el.style.removeProperty('--clip-pos')
    }
  }
  el.addEventListener('transitionend', onTransitionend)
  el.addEventListener('transitioncancel', onTransitionend)
}

function hasScrollbar (el) {
  if (!el || el.nodeType !== Node.ELEMENT_NODE) return false

  const style = window.getComputedStyle(el)
  return style.overflowY === 'scroll' || (style.overflowY === 'auto' && el.scrollHeight > el.clientHeight)
}
.app-copy
  position: fixed !important
  z-index: -1 !important
  pointer-events: none !important
  contain: size style !important
  overflow: clip !important

.app-transition
  --clip-size: 0
  --clip-pos: 0 0
  clip-path: circle(var(--clip-size) at var(--clip-pos))
  transition: clip-path .35s ease-out

Answer №2

Here's a straightforward example to demonstrate this concept:

<template>
  <v-app :dark="darkMode">
    <v-btn @click="toggleDarkMode"&>
      Toggle Dark Mode
    </v-btn>
    <v-main>
      <transition name="circle-expand">
        <div :class="themeClass" v-if="isThemeVisible">
          <!-- Your content goes here -->
        </div>
      </transition>
    </v-main>
  </v-app>
</template>

<script>
export default {
  data() {
    return {
      darkMode: false,
      isThemeVisible: true
    };
  },
  computed: {
    themeClass() {
      return {
        'theme--light': !this.darkMode,
        'theme--dark': this.darkMode
      };
    }
  },
  methods: {
    toggleDarkMode() {
      this.isThemeVisible = false; // Hide content during transition
      setTimeout(() => {
        this.darkMode = !this.darkMode; // Toggle dark mode
        this.isThemeVisible = true; // Show content after theme change
      }, 500); // Adjust delay to match transition duration
    }
  }
};
</script>

<style>
.circle-expand-enter-active,
.circle-expand-leave-active {
  transition: all 0.5s ease;
}

.circle-expand-enter,
.circle-expand-leave-to {
  opacity: 0;
}

.circle-expand-enter {
  transform: scale(0);
}

.circle-expand-leave-to {
  transform: scale(2);
}
</style>

By clicking the button, you can switch the darkMode boolean, which alters the theme of the entire Vuetify application. The transition effect remains consistent, smoothly transitioning the theme change with an expanding circle animation. Feel free to tweak the transition duration and effects according to your preferences.

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

javax.el.MethodNotFoundException: JSP method cannot be located

While compiling the JSP template, I encountered the following exception: The method getFriendImage() is not recognized I attempted to rebuild the class without success. The src attribute contains a valid link to an image that is functioning properly. ...

NextJS compilation sometimes results in undefined errors when using Sass styles

My peace lies in the sass code: .link display: inline-table text-align: center align-self: center margin: 5px 15px font-size: 20px color: black text-decoration: none transition: 0.1s ease-in-out .link:hover text-decoration: underline .l ...

Issues with the Content Editable Functionality

While working on my website, I encountered a strange issue. For some reason, I can't seem to get the contenteditable="true" attribute to work on the heading with the ID "hl". It would be awesome if someone could help me figure out how to mak ...

Failure to include jQuery and CSS in the AJAX response

Having trouble with my slider. The script that is added at runtime is not showing up in the ajax response. Does anyone know why this might be happening? $.ajax({ url:"ajax_get_response_eng_to_change.php", type:"POST", async:true, data:{c ...

What does the term "the selected font size" mean when defined in em units?

Exploring the query raised in this post: Why em instead of px?, the top voted response sheds light on the nature of em: The unit 'em' is not fixed but rather relative to the current font size. It varies according to the user's chosen font p ...

What is the best way to create a border for the active class nav-item in Bootstrap 4?

I am facing an issue with changing styles in the nav-item. I have created a separate style sheet and added the necessary code to make the changes. Surprisingly, the changes are reflecting well in Internet Explorer but not in Firefox. Can anyone guide me on ...

What is the best way to assign a class to a child element when the rest of the children are not visible

Looking for a CSS-only solution, I have a simple task involving filtering div elements. The goal is to display an error message when none of the filtered divs match the selected criteria. HTML Structure: <div id="listHolder"> <div class="lis ...

"Enhancing Your List Design: Customizing CSS for Hover Effects on Images

I'm currently working on a CSS/HTML list that utilizes an image as the background and changes to a different image when hovered over. However, I've encountered an issue where the hover image is not aligning properly, leaving a noticeable gap. T ...

Tips for eliminating the shadow effect from a textbox using CSS

I need assistance with removing the shadowed edges on a textbox in an existing project. Can anyone provide guidance on how to remove this effect? Thank you for your help! ...

Froala text area is unexpectedly visible when I attempt to cover it with a partially see-through mask

The website I'm currently developing features a semi-transparent overlay that dims the screen with a light-colored message displayed on top. This overlay and message are shown whenever there is a background process running. Everything was running smoo ...

Issue with Vue.js: The input value text is not being updated

In my current setup, I am dealing with an input component that is linked to a variable called 'searchText' in the parent component. This variable stores the text value of the search input. The {{searchText}} in the template updates accurately bas ...

Refreshing the page after making an API call does not result in updated data

Within my VueJS application, I have implemented an API call to retrieve user statistics for display. The process involves a straightforward Java backend and utilizing an $axios get request. Upon initial page load, everything functions as anticipated. Howev ...

Align watermark content to the far left side

Having trouble getting my watermark to align properly on the left side of my website's main content. Here is how it currently looks: https://i.sstatic.net/Nfhh5.png The issue arises when I resize the screen or switch to mobile view, as the watermark ...

Creating a CSS triangle that smoothly transitions between two different colors

Is it possible to create a triangle in CSS that smoothly transitions between two colors without relying on a hover state? .arrow-down { width: 0; height: 0; border-left: 20px solid transparent; border-right: 20px solid transparent; b ...

Web Development: Custom Search Form

I am looking to create a website with a form similar to this one, but I'm struggling to figure out how to incorporate all three components into a single form using only HTML and CSS - no javascript. Do you have any suggestions or advice on how to achi ...

I am looking for a way to write a function that will emit an event to the parent component when an icon is

I am creating an input element with an icon to show/hide the password. How can I emit an event in the v-icon to notify the parent component when the icon is clicked? Here is my child component BaseInputPassword: <div class="label"> ...

Enhancing user experience by highlighting invalid input fields with a red border using client-side HTML5 validation and Bootstrap 5.2

When reviewing the Bootstrap 5.2 validation documentation, https://getbootstrap.com/docs/5.2/forms/validation/ "It has come to our attention that the client-side custom validation styles and tooltips are not accessible at this time, as they are not ...

How to Alter the Color of a Hyperlink in HTML

I've been working on an angular2 application and I'm using a hyperlink to trigger a method that opens a small popup window. I've tried different methods to change the color of the link when it's clicked, but so far none have worked. Th ...

Controlling the Vuetify Navigation drawer from a separate component

Within my App.vue component, I have a Navigation Drawer menu containing main items and some subitems. These subitems are meant to be displayed only after a specific event, such as a button click on the child component's page (when the variable totalRe ...

Even with the v-once directive applied, the component will still update whenever there is a change in the value of a property within a

Why does a component with the v-once directive re-render when passing an object as a prop and changing its property value? What causes the internal Watcher of the component to trigger a re-render? I have observed that when passing a primitive as a prop, t ...