How to animate a border using JS for a timer card

Viewed 576

I have the following timer card for a timer app that I am making and I want to have a border slowly wrap around it as it counts down similar to this code pen. https://codepen.io/Mamboleoo/pen/zYOJOGb The progress has to be controlled by JS.

enter image description here

I am using VueJS with Vuetify and here is the code that I have.

      <v-col
          cols="12"
         sm="4"
         xs="4" v-for="timer in formattedTimers" :key="timer.id">

    <v-card :class="{jiggle : editmode}" max-width="200" class="mx-auto" outlined>
      <v-list-item three-line>
        <v-list-item-content>
          <div class="headline mb-8 text-center">{{ timer.name }}</div>
          <v-list-item-title class="headline mb-4 text-center">{{ parseTime(timer.timeLeft) }}</v-list-item-title>
        </v-list-item-content>
      </v-list-item>

      <v-card-actions>
        <div v-if="!editmode">
          <v-btn @click="zeroTimer(timer.id)" left>Zero</v-btn>
          <v-btn color="primary" @click="resetTimer(timer.id)" right absolute>Reset</v-btn>
        </div>
        <div v-else>
          <v-btn color="primary" @click="deleteTimer(timer.id)" left>Delete</v-btn>
        </div>
      </v-card-actions>
    </v-card>
  </v-col>
2 Answers

You can directly use the SVG in the demo in Codepen you attached.

In order to make the border-image to be re-rendered when Reset is clicked, one trick is add empty space to the SVG string then convert it base64.

Below is one simple snippet:

Vue.component('v-timer',{
  render (h) {
    return h('div', {
      style: {
        border: '10px solid black',
        borderImage: `url("data:image/svg+xml;base64,${this.computedSVGUrl}") 1`
      }
    }, [h('span', {}, `${this.inner} Secs`), h('button', {
      on: {
        click: () => {
          this.inner =  this.seconds
          this.spaces += ' '
          this.startTimer(this.seconds)
        }
      }
    }, 'Reset')])
  },
  props: {
    'seconds': {
      type: Number,
      default: 0
    }
  },
  data () {
    return {
      spaces: '',
      inner: 0,
      intervalCtrl: null
    }
  },
  computed: {
    computedSVGUrl: function () {
      return window.btoa(`<svg width='100' height='100' viewBox='0 0 100 100' fill='none' xmlns='http://www.w3.org/2000/svg'> <style>path{animation:stroke ${this.seconds}s linear;}@keyframes stroke{to{stroke-dashoffset:388;}}</style><linearGradient id='g' x1='0%' y1='0%' x2='0%' y2='100%'><stop offset='0%' stop-color='#2d3561' /><stop offset='25%' stop-color='#c05c7e' /><stop offset='50%' stop-color='#f3826f' /><stop offset='100%' stop-color='#ffb961' /></linearGradient> <path d='M1.5 1.5 l97 0l0 97l-97 0 l0 -97' stroke-linecap='square' stroke='url(#g)' stroke-width='3' stroke-dasharray='388'/> ${this.spaces} </svg>`)
    }
  },
  watch: {
    seconds: {
      handler: function (newVal) {
        this.inner = newVal
        this.startTimer(newVal)
      },
      immediate: true
    }
  },
  mounted: function () {
    this.startTimer(this.seconds)
  },
  methods: {
    startTimer: function (seconds) {
      this.resetInterval()
      this.intervalCtrl = setInterval(() => {
        this.inner -= 1
        this.inner <= 0 && this.resetInterval()
      }, 1000)
    },
    resetInterval: function () {
      this.intervalCtrl && clearInterval(this.intervalCtrl)
      this.intervalCtrl = null
    }
  }
})
    
new Vue ({
  el:'#app'
})
.timer {
  width: 200px;
  height: 30px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
    <div>
        <div>
            <v-timer :seconds="5" class="timer"></v-timer>
            <v-timer :seconds="10" class="timer"></v-timer>
            <v-timer :seconds="15" class="timer"></v-timer>
        </div>
    </div>
</div>

There are several technique how to make animation border. One of them: You can create four empty span tag inside you box:

<v-card>
  <span></span>
  <span></span>
  <span></span>
  <span></span>
</v-card>

They represent border sides. They should be absolutely positioned to the card. Then you should provide a keyframe infinite animation tweaking span's width one by one. All this css logic should be binded to one class. Then in your Vue component apply this class dynamically according to your counter logic.

Related