4

We are developing a website with unique navigation. Part of it involves on each scroll either up or down, it fires JavaScript and navigates to a different HTML element. It is in Vue.js / Nuxt.

So far, everything works beautifully, minus the usage of the trackpad. The initial 2-finger swipe with the trackpad works -- however, this seems to initiate some sort of a smooth scroll, which takes a while to complete. If you try to 2-finger swipe again in the same direction, it's treated as one long scroll, which doesn't fire the JavaScript to advance to the next page. I did a console log of the deltaY and since it's a 2-finger swipe (smooth scroll?), the deltas take a second or two to finish.

This causes issues since you can't use the trackpad and swipe through sections quickly. You have to swipe down, wait until the scroll finishes, then swipe down again.

How would we fix this issue? Is there a way to kill the current scroll, or to eliminate smooth scrolling and just have scrolls go 100 deltas in one way or the other?

Thanks in advance

Vue.js code

export default {
  layout: 'empty',
  components: {
    Footer,
    Logo,
    LottieAnimation,
    ServiceIcon,
    CloseBtn
  },
  async asyncData({ app }) {
    try {
      return await app.$api.$get('/services.json')
    } catch (error) {
      return {
        error
      }
    }
  },
  data() {
    return {
      activePage: 0,
      config: {},
      error: null,
      goToPageTimer: null,
      isTouchpad: null,
      page: {},
      scrollDisabled: false,
      scrollPercentage: 0,
      scrollPercentageOld: 0,
      scrollDirection: null,
      scrollTimer: null,
      touchStart: null,
      touchTimer: null,
      lastWheelDirection: null,
      lastWheelEvent: null,
      wheelEventsCount: 0,
      wheelEventsLimit: 25,
      wheelStopTime: 120
    }
  },
  watch: {
    scrollPercentage(newValue, oldValue) {
      this.scrollDirection = newValue > oldValue ? 'bottom' : 'top'

      if (this.scrollDirection === 'bottom') {
        this.goToNextPage()
      } else {
        this.goToPreviousPage()
      }
    }
  },
  mounted() {
    window.addEventListener('keydown', this.onKeyDown)
    window.addEventListener('scroll', this.handleScroll)
    window.addEventListener('wheel', this.onMouseWheel)
    window.addEventListener('touchend', this.onTouchEnd)
    window.addEventListener('touchstart', this.onTouchStart)
    this.initPage()
  },
  destroyed() {
    window.removeEventListener('keydown', this.onKeyDown)
    window.removeEventListener('scroll', this.handleScroll)
    window.removeEventListener('wheel', this.onMouseWheel)
    window.removeEventListener('touchend', this.onTouchEnd)
    window.removeEventListener('touchstart', this.onTouchStart)
  },
  methods: {
    disableScrolling() {
      this.scrollDisabled = true

      if (this.goToPageTimer) {
        clearTimeout(this.goToPageTimer)
      }

      this.goToPageTimer = setTimeout(() => {
        this.scrollDisabled = false
      }, 30)
    },
    getServicePageId(slug) {
      let servicePageId = null
      Object.keys(this.page.childPages).forEach((pageId) => {
        if (this.page.childPages[pageId].slug === slug) {
          servicePageId = parseInt(pageId)
        }
      })
      return servicePageId
    },
    goToNextPage() {
      if (
        this.scrollDisabled ||
        this.activePage === this.page.childPages.length
      ) {
        return
      }

      this.activePage = this.activePage === null ? 0 : this.activePage + 1

      if (this.activePage < this.page.childPages.length) {
        this.scrollToActivePage()
      }
    },
    goToPreviousPage() {
      if (this.scrollDisabled) {
        return
      }

      if (!this.activePage) {
        return
      }

      this.activePage -= 1
      this.scrollToActivePage()
    },
    goToPage(index) {
      if (this.scrollDisabled) {
        return
      }

      this.activePage = index
      this.scrollToActivePage()
    },
    handleScroll() {
      // If scrolling to top do nothing
      if (!window.scrollY) {
        return
      }

      if (this.activePage < this.page.childPages.length - 1) {
        window.scrollTo(0, 0)
      }
    },
    initPage() {
      if (this.$route.query.service) {
        this.activePage = this.getServicePageId(this.$route.query.service)
      }
    },
    isServiceActiveOrIsLatestService(slug) {
      const servicePageId = this.getServicePageId(slug)

      // Service is active
      if (this.activePage === servicePageId) {
        return true
      }

      const latestServicePageId = this.page.childPages.length - 1

      // Service is the latest and active page is over it (user is looking the footer)
      return (
        servicePageId === latestServicePageId &&
        this.activePage > latestServicePageId
      )
    },
    onKeyDown(e) {
      const nextPageKeys = [
        34, // Page down
        39, // Arrow right
        40 // Arrow down
      ]

      const previousPageKeys = [
        33, // Page up
        37, // Arrow left
        38 // Arrow up
      ]

      if (nextPageKeys.includes(e.keyCode)) {
        this.goToNextPage()
      } else if (previousPageKeys.includes(e.keyCode)) {
        this.goToPreviousPage()
      }
    },
    onMouseWheel(event) {
      const now = +new Date()

      const millisecondsSinceLastEvent = this.lastWheelEvent
        ? now - this.lastWheelEvent
        : 0

      const eventsLimitReached = this.wheelEventsCount > this.wheelEventsLimit
      const stopTime = this.wheelStopTime
      const delta = Math.sign(event.deltaY)
      const directionChanged = delta !== 0 && this.lastWheelDirection !== delta

      this.lastWheelEvent = now

      if (directionChanged) {
        this.lastWheelDirection = delta
      }

      if (
        !directionChanged &&
        !eventsLimitReached &&
        millisecondsSinceLastEvent &&
        millisecondsSinceLastEvent <= stopTime
      ) {
        this.wheelEventsCount += 1

        return
      }

      this.wheelEventsCount = 0

      if (delta === -1) {
        this.goToPreviousPage()
      } else {
        this.goToNextPage()
      }
    },
    onTouchEnd(e) {
      const now = +new Date()
      const touchEndX = e.changedTouches[0].clientX
      const touchEndY = e.changedTouches[0].clientY

      // Try to guess single touch based on touch start - touch end time
      const isSingleTouch = now - this.touchTimer <= 100

      // Single touch
      if (isSingleTouch && this.touchStart.x === touchEndX) {
        const horizontalPercentage = Math.ceil(
          (touchEndX * 100) / window.innerWidth
        )
        const verticalPercentage = Math.ceil(
          (touchEndY * 100) / window.innerHeight
        )

        if (horizontalPercentage <= 40) {
          this.goToPreviousPage()
          return
        }

        if (horizontalPercentage >= 60) {
          this.goToNextPage()
          return
        }

        if (verticalPercentage <= 40) {
          this.goToPreviousPage()
          return
        }

        if (verticalPercentage >= 60) {
          this.goToNextPage()
          return
        }
      }

      // Touch move
      if (this.touchStart.y > touchEndY + 5) {
        this.goToNextPage()
      } else if (this.touchStart.y < touchEndY - 5) {
        this.goToPreviousPage()
      }
    },
    onTouchStart(e) {
      this.touchTimer = +new Date()
      this.touchStart = {
        x: e.touches[0].clientX,
        y: e.touches[0].clientY
      }
    },
    onVisibilityChanged(isVisible, entry) {
      if (isVisible) {
        entry.target.classList.add('dynamic-active')
      } else {
        entry.target.classList.remove('dynamic-inactive')
      }
    },
    scrollToActivePage() {
      this.scrollToService(this.page.childPages[this.activePage].slug)
    },
    scrollToRef(ref) {
      if (!this.$refs[ref]) {
        return
      }
      if (this.$refs[ref][0]) {
        this.$scrollTo(this.$refs[ref][0], 100, {
          force: true,
          cancelable: false
        })
      } else {
        this.$scrollTo(this.$refs[ref], 100, { force: true, cancelable: false })
      }

      this.disableScrolling()
    },
    scrollToService(slug) {
      this.scrollToRef(`service-${slug}`)
    },
    serviceBgImageUrl(service) {
      return require(`~/assets/img/services/backgrounds/${service.slug}.jpg`)
    }
  },
  head() {
    const data = {
      bodyAttrs: {
        class: 'services'
      }
    }

    if (this.page.data) {
      data.title = this.page.data.title
      data.meta = this.page.metadata
    }

    return data
  }
}
hdwebpros
  • 528
  • 3
  • 7
  • 20

0 Answers0