Skip to content

Latest commit

 

History

History
396 lines (288 loc) · 8.98 KB

File metadata and controls

396 lines (288 loc) · 8.98 KB
theme title colorSchema exportFilename download globalBottomPosition
mokkapps
Building a Polite Popup with Nuxt 3
light
vuejs-nation-2023-lightning-talk-polite-popup
true
left

Building a Polite Popup with Nuxt 3

Vue.js Nation 2023 - Lightning Talk


layout: about-me


layout: section

What is a "Impolite Popup"?


Demo Time

Source Code

Let's take a look at an example of an "impolite popup"

  • Problem 1: Another annoying full-page popup on the landing page
  • Problem 2: Visitors haven't engaged with the related content
  • Problem 3: Visitors are asked every time they visit the page

layout: section

What is a "Polite Popup"?


Let's build a polite popup

A polite popup appears to visitors if they

  • are visiting a page with Vue-related content as the newsletter targets Vue developers
  • are actively scrolling the current page for 6 seconds or more
  • scroll through at least 35% of the current page during their visit

Coding Time

<style> h1 { color: white !important; @apply !text-shadow-lg; @apply !text-center; @apply !text-8xl } </style>

Vue Composable

Let's start by writing a Vue composable for our polite popup:

export const usePolitePopup = () => {
  const visible = ref(false);

  const trigger = () => {}

  return {
    visible,
    trigger,
  };
};

Track time spent on page

The visitor must be actively scrolling the current page for 6 seconds or more.

import { useTimeoutFn } from '@vueuse/core'

const config = { timeoutInMs: 6000 } as const

export const usePolitePopup = () => {
  const visible = ref(false);
  const readTimeElapsed = ref(false)

  const { start } = useTimeoutFn(
    () => {
      readTimeElapsed.value = true
    },
    config.timeoutInMs,
    { immediate: false }
  )

  const trigger = () => {
    readTimeElapsed.value = false
    start()
  }

  return {
    visible,
    trigger,
  };
};

Track scroll progress

The visitor must scroll through at least 35% of the current page during their visit.

import { useWindowSize, useWindowScroll } from '@vueuse/core'

const config = { timeoutInMs: 6000, contentScrollThresholdInPercentage: 35, } as const

export const usePolitePopup = () => {
    //...
    const { height: windowHeight } = useWindowSize()
    const { y: scrollTop } = useWindowScroll()

    // Returns percentage scrolled (ie: 80 or NaN if trackLength == 0)
    const amountScrolledInPercentage = computed(() => {
      const documentScrollHeight = document.documentElement.scrollHeight
      const trackLength = documentScrollHeight - windowHeight.value
      const scrollPercent = scrollTop.value / trackLength;
      const scrollPercentRounded = Math.floor(scrollPercent * 100);
      return scrollPercentRounded;
    })

    const scrolledContent = computed(() => {
      return amountScrolledInPercentage.value >= config.contentScrollThresholdInPercentage
    )}

    return {
        visible,
        trigger,
    }
}


Trigger visible

We have now all information available to update the visible reactive variable:

export const usePolitePopup = () => {
    const visible = ref(false)
    const readTimeElapsed = ref(false)
    //...

    const scrolledContent = computed(() => amountScrolledInPercentage.value >= config.contentScrollThresholdInPercentage)

    watch([readTimeElapsed, scrolledContent], ([newReadTimeElapsed, newScrolledContent]) => {
        if (newReadTimeElapsed && newScrolledContent) {
            visible.value = true
        }
    })

    return {
        visible,
        trigger,
    }
}

Wait Before the Popup Appears Again

import { useLocalStorage } from '@vueuse/core'

interface PolitePopupStorageDTO {
  status: 'unsubscribed' | 'subscribed'
  seenCount: number
  lastSeenAt: number
}

export const usePolitePopup = () => {
  //...
  const storedData: Ref<PolitePopupStorageDTO> = useLocalStorage('polite-popup', {
    status: 'unsubscribed',
    seenCount: 0,
    lastSeenAt: 0,
  })
  //...
  watch(
    [readTimeElapsed, scrolledContent],
    ([newReadTimeElapsed, newScrolledContent]) => {
      if (newReadTimeElapsed && newScrolledContent) {
        visible.value = true;
        storedData.value.seenCount += 1;
        storedData.value.lastSeenAt = new Date().getTime();
      }
    }
  );
  //...
  return {
    visible,
    trigger
  }
}

Trigger timer

In [..slug].vue we trigger the timer if the route path is equal to /vue:

<template>
  <main>
    <ContentDoc />
  </main>
</template>

<script setup lang="ts">
const route = useRoute();

const { trigger } = usePolitePopup();

if (route.path === "/vue") {
  trigger();
}
</script>

We're done!

We implemented the main logic for a polite popup in Nuxt 3 💪🏻

Thanks to the amazing people behind


layout: article

You want more?

For more details read the corresponding blog post

Blog Post Image


layout: outro

Thank you for listening!

Questions?

Repository / Slides