Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to animate a border using JS for a timer card

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>
like image 556
ThatPurpleGuy Avatar asked Nov 05 '25 00:11

ThatPurpleGuy


1 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>
like image 192
Sphinx Avatar answered Nov 06 '25 17:11

Sphinx



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!