Search code examples
iosvue.jssafarinuxt.jstailwind-css

Changing Vue (Nuxt 3) reactive text creates visual artifacts of the previous characters on Safari


I am using Nuxt 3 for a project and I have created a countdown component. The countdown code is a simple setInterval with Nuxt ClientOnly tags around it to prevent hydration mismatch errors. The CountdownItem simply displays its property value.

Countdown template:

  <div>
    <h2 class="w-full text-center uppercase">
      {{ $t("message.countdownTitle") }}
    </h2>
    <ClientOnly>
      <div class="flex justify-center w-full gap-x-1">
        <CountdownItem :number="days" text="days" />
        <CountdownItem :number="hours" text="hours" />
        <CountdownItem :number="minutes" text="minutes" />
        <CountdownItem :number="seconds" text="seconds" />
      </div>
    </ClientOnly>
  </div>

Countdown script

<script setup lang="ts">
import type { StrapiCountdown } from "~/types/api";
const { findOne } = useStrapi();

const { data: countdownData } = await useAsyncData("countdown", () =>
  findOne<StrapiCountdown>("countdown")
);

const countDownDate = new Date(
  countdownData.value?.data.attributes.date as string
).getTime();

var now = new Date().getTime();
var distance = countDownDate - now;

const days = ref<number>(Math.floor(distance / (1000 * 60 * 60 * 24)));
const hours = ref<number>(
  Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
);
const minutes = ref<number>(
  Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60))
);
const seconds = ref<number>(Math.floor((distance % (1000 * 60)) / 1000));

setInterval(setCountdownVariables, 1000);

function setCountdownVariables() {
  // Get today's date and time
  var now = new Date().getTime();

  // Find the distance between now and the count down date
  var distance = countDownDate - now;

  // Time calculations for days, hours, minutes and seconds
  days.value = Math.floor(distance / (1000 * 60 * 60 * 24)) || 0;
  hours.value =
    Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) || 0;
  minutes.value = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)) || 0;
  seconds.value = Math.floor((distance % (1000 * 60)) / 1000) || 0;
}
</script>

CountdownItem template

  <div
    class="flex flex-col items-center justify-center flex-none w-20 h-20 bg-opacity-60 rounded-2xl bg-lightPurple"
  >
    <span class="mr-2 text-4xl font-bold antialiased">{{ number }}</span>
    <span class="text-xs font-bold">{{ text }}</span>
  </div>

The code works fine on every device except on iOS Safari. On iOS it creates this visual bug: Visual artifact on text update

It happens when the text updates to a different number. The boundaries of the previous number get left behind as a visual artifact.

The font is Prompt Bold Italic if it helps.


Solution

  • It was an issue with rendering bold italic fonts. The project is using the nuxt/google-fonts module. I had set it to import Prompt with multiple font weights. However, I had not set it to import italic font weights. Importing that fixed the problem:

    I changed this:

    "@nuxtjs/google-fonts",
          {
            families: {
              "Prompt": {
                wght: [300, 400, 600, 700],
              },
            },
          },
    

    To this:

    "@nuxtjs/google-fonts",
          {
            families: {
              "Prompt": {
                wght: [300, 400, 600, 700],
                ital: [300, 400, 600, 700], // Added this
              },
            },
          },