Commit c390a069 authored by Konrad Mohrfeldt's avatar Konrad Mohrfeldt
Browse files

improve ux for pull-to-refresh

pull-to-refresh now shows a little indicator when it’s used and animates
once it’s triggered.
parent cec5989c
Loading
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
<template>
  <div :style="style">
    <Refresh/>
    <NavBar/>
    <router-view :key="routerViewKey"></router-view>
    <ProviderData/>
@@ -12,6 +13,7 @@
  import NavBar from './components/nav/NavBar'
  import { oneOf } from './util'
  import ProviderData from './components/nav/ProviderData'
  import Refresh from './components/generic/Refresh'

  function isTextField (field) {
    if (!field) { return false }
@@ -25,7 +27,12 @@

  export default {
    name: 'App',
    components: { ProviderData, NavBar, Player },
    components: {
      NavBar,
      Player,
      ProviderData,
      Refresh
    },
    data () {
      return {
        hasFocus: isTextField(document.activeElement)
+78 −0
Original line number Diff line number Diff line
<template>
  <div class="refresh" role="presentation">
    <div class="refresh-indicator" :style="indicatorStyle" @animationend="animation = null">
      <RefreshCwIcon/>
    </div>
  </div>
</template>

<script>
  import { RefreshCwIcon } from 'vue-feather-icons'
  import { pullRefreshController as refresh } from '../../util/dom'

  export default {
    components: { RefreshCwIcon },
    data () {
      return {
        refresh,
        animation: null
      }
    },
    computed: {
      indicatorStyle () {
        return {
          transition: !this.animation ? 'transform .05s' : null,
          transform: !this.animation ? `translateY(${-100 + refresh.triggerProgress * 1.75}%)` : null,
          animation: this.animation ? `${this.animation} 1s linear forwards` : null
        }
      }
    },
    watch: {
      'refresh.isRefreshing' (isRefreshing) {
        if (isRefreshing) {
          this.animation = 'refresh-spin'
        }
      }
    }
  }
</script>

<style lang="scss">
  @import '../../styles/variables';

  .refresh {
    display: flex;
    justify-content: center;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: auto;
    pointer-events: none;
    z-index: 1000;
  }

  .refresh-indicator {
    background: $color-surface;
    width: 44px;
    height: 44px;
    color: white;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 0 4px rgba(0, 0, 0, .20);
  }

  @keyframes refresh-spin {
    0% {
      transform: translateY(75%) rotate(0);
    }
    50% {
      transform: translateY(25%) rotate(360deg);
    }
    100% {
      transform: translateY(-100%) rotate(360deg);
    }
  }
</style>
+24 −8
Original line number Diff line number Diff line
import { debounce } from 'throttle-debounce'
import Vue from 'vue'

export const pullRefreshController = (() => {
  let startY
  const TRIGGER_OFFSET_MAX = 150
  let startY = null

  const refresh = debounce(350, false, () => {
    for (const subscriber of subscribers) {
      subscriber.refresh()
    }
  const notifySubscribers = debounce(350, false, () => {
    return Promise.all(subscribers.map(s => s.refresh()))
      .finally(() => { refresh.isRefreshing = false })
  })

  document.body.addEventListener('touchstart', e => {
@@ -15,13 +16,27 @@ export const pullRefreshController = (() => {

  document.body.addEventListener('touchmove', e => {
    const y = e.touches[0].pageY
    if (!startY || refresh.isRefreshing || subscribers.length === 0) return
    if (document.scrollingElement.scrollTop === 0 && y > startY) {
      refresh()
      const triggerOffset = y - startY
      refresh.triggerProgress = (triggerOffset / TRIGGER_OFFSET_MAX) * 100
      if (triggerOffset > TRIGGER_OFFSET_MAX) {
        refresh.isRefreshing = true
        refresh.triggerProgress = 0
        notifySubscribers()
      }
    }
  }, { passive: true })

  document.body.addEventListener('touchend', e => {
    refresh.triggerProgress = 0
    startY = null
  }, { passive: true })

  const subscribers = []
  return {
  const refresh = Vue.observable({
    triggerProgress: 0,
    isRefreshing: false,
    subscribe (subscriber) {
      subscribers.push(subscriber)
    },
@@ -31,5 +46,6 @@ export const pullRefreshController = (() => {
        subscribers.splice(index, 1)
      }
    }
  }
  })
  return refresh
})()