preface

The effect came from a screensaver software called World-Clock. I thought it was so interesting that I decided to masturbating myself late at night.

Results the preview

Online preview: Compass clock

Preview screenshot:

implement

The first step is to arrange multiple text elements in a circle, and then rotate the circle at a certain Angle to achieve the effect.

The next step is to implement how to arrange the text in a circle. Once you’ve done that, the rest is easy.

Arrange the text in a circle

Next, let’s implement the outermost “second” step by step. Seconds have 60 elements ranging from 0 to 59, first positioning them in a square div.

<template>
  <div class="home">
    <! - seconds -- -- >
    <div class="box-wrapper">
      <div class="circle-box" :style="boxStyle('seconds')">
        <span
          v-for="(item, index) in secondTexts"
          :key="item"
        >{{ item }}</span>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Home'.data() {
    return {
      secondTexts: [
        'zero zero'.'a second'.'two seconds'.'three seconds'.'four seconds'.'five seconds'.'six seconds'.'seven seconds'.'eight seconds'.'nine seconds'.'ten seconds'.'Eleven seconds'.'Twelve seconds'.'Thirteen seconds'.'Fourteen seconds'.'Fifteen seconds'.'Sixteen seconds'.'Seventeen seconds'.'Eighteen seconds'.'Nineteen seconds'.'Twenty seconds'.'Twenty-one seconds'.'Twenty-two seconds'.'twenty-three seconds'.'twenty-four seconds'.'twenty-five seconds'.'Twenty-six seconds'.'Twenty-seven seconds'.'Twenty-eight seconds'.'Twenty-nine seconds'.'Thirty seconds'.'Thirty-one seconds'.'Thirty-two seconds'.'Thirty-three seconds'.'Thirty-four seconds'.'35 seconds'.'Thirty-six seconds'.'Thirty-seven seconds'.'Thirty-eight seconds'.'Thirty-nine seconds'.'Forty seconds'.'Forty-one seconds'.'Forty-two seconds'.'Forty-three seconds'.'Forty-four seconds'.'Forty-five seconds'.'Forty-six seconds'.'Forty-seven seconds'.'Forty-eight seconds'.'Forty-nine seconds'.'Fifty seconds'.'Fifty-one seconds'.'Fifty-two seconds'.'Fifty-three seconds'.'Fifty-four seconds'.'Fifty-five seconds'.'Fifty-six seconds'.'Fifty-seven seconds'.'Fifty-eight seconds'.'Fifty-nine seconds'].// Box size
      boxSize: {
        seconds: 580}}},methods: {
    // Set the width and height of the text box
    boxStyle(key) {
      return {
        width: this.boxSize[key] + 'px'.height: this.boxSize[key] + 'px'}}}}</script>

<style lang="scss" scoped>
.home {
  height: 100%;
  width: 100%;
  background-color: # 000000;
  color: #71767D;
  position: relative;
  min-width: 800px;
  min-height: 660px;
  padding: 20px 0;
  overflow: hidden;
}

.box-wrapper {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.circle-box {
  position: relative;
  border: 1px solid red;
}

.circle-box span {
  white-space: nowrap;
  font-size: 14px;
  position: absolute;
}
</style>
Copy the code

Now the text is stacked together after setting absolute positioning, and the next step is to make it circular through position calculation.

From the figure, we know that to arrange text on the circle, we need to know the x-coordinate and y-coordinate of the text elements to point O (center of the circle), that is, the height of a and the length of B, which corresponds to the top and left values we want to set.

We know the radius, that is, half the width of the box, r = 580/2 = 290, and the Angle between each element and the center of the circle, c = (360/60) * I, from which we can figure out the values of a and B according to the mathematical formula. The new code is as follows:

<template>
  <div class="home">
    <! - seconds -- -- >
    <div class="box-wrapper">
      <div class="circle-box" :style="boxStyle('seconds')">
        <span
          v-for="(item, index) in secondTexts"
          :key="item"
          :style="spanStyle(boxSize.seconds, secondTexts, index)"
        >{{ item }}</span>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Home'.methods: {
    spanStyle(size, texts, i) {
      const r = size / 2 / / radius
      const deg = this.getPerDeg(texts) // The average degree of element spacing
      const angle = i * deg / / the Angle
      const { a, b } = this.getHypotenuse(r, angle)
      const rotateDeg = deg * i // The rotation Angle of the text
      return {
        top: a + r + 'px'.left: b + r + 'px'.transform: `rotate(${rotateDeg}deg)`.transformOrigin: '0 0'}},// The average degree of element spacing
    getPerDeg(texts) {
      return 360 / texts.length
    },

    // Get the Angle and the hypotenuse
    getHypotenuse(long, angle) {
      // Get radians
      let radian = 2 * Math.PI / 360 * angle
      return {
        a: Math.sin(radian) * long, / / adjacent side
        b: Math.cos(radian) * long / / on the edge}}}}</script>
Copy the code

Good effect, then play CV method, the latitude to get up minutes. The new code is as follows:

<template>
  <div class="home">
    <!-- 分 -->
    <div class="box-wrapper">
      <div class="circle-box" :style="boxStyle('minutes', minutesDeg)">
        <span
          v-for="(item, index) in minuteTexts"
          :key="item"
          :style="spanStyle(boxSize.minutes, minuteTexts, index)"
        >{{ item }}</span>
      </div>
    </div>
    <! - seconds -- -- >
    <div class="box-wrapper">
      <div class="circle-box" :style="boxStyle('seconds', secondsDeg)">
        <span
          v-for="(item, index) in secondTexts"
          :key="item"
          :style="spanStyle(boxSize.seconds, secondTexts, index)"
        >{{ item }}</span>
      </div>
    </div>
  </div>
</template>

<script>

export default {
  name: 'Home'.data() {
    return {
      secondTexts: [
        'zero zero'.'a second'.'two seconds'.'three seconds'.'four seconds'.'five seconds'.'six seconds'.'seven seconds'.'eight seconds'.'nine seconds'.'ten seconds'.'Eleven seconds'.'Twelve seconds'.'Thirteen seconds'.'Fourteen seconds'.'Fifteen seconds'.'Sixteen seconds'.'Seventeen seconds'.'Eighteen seconds'.'Nineteen seconds'.'Twenty seconds'.'Twenty-one seconds'.'Twenty-two seconds'.'twenty-three seconds'.'twenty-four seconds'.'twenty-five seconds'.'Twenty-six seconds'.'Twenty-seven seconds'.'Twenty-eight seconds'.'Twenty-nine seconds'.'Thirty seconds'.'Thirty-one seconds'.'Thirty-two seconds'.'Thirty-three seconds'.'Thirty-four seconds'.'35 seconds'.'Thirty-six seconds'.'Thirty-seven seconds'.'Thirty-eight seconds'.'Thirty-nine seconds'.'Forty seconds'.'Forty-one seconds'.'Forty-two seconds'.'Forty-three seconds'.'Forty-four seconds'.'Forty-five seconds'.'Forty-six seconds'.'Forty-seven seconds'.'Forty-eight seconds'.'Forty-nine seconds'.'Fifty seconds'.'Fifty-one seconds'.'Fifty-two seconds'.'Fifty-three seconds'.'Fifty-four seconds'.'Fifty-five seconds'.'Fifty-six seconds'.'Fifty-seven seconds'.'Fifty-eight seconds'.'Fifty-nine seconds'].minuteTexts: [
        'zero zero'.'one'.'binary'.'3'.'four points'.'five'.'six points'.'seven'.'eight'.'nine points'.'very'.'Eleven points'.'Twelve points'.'Thirteen minutes'.'Fourteen points'.'Fifteen minutes'.'Sixteen minutes'.'Seventeen minutes'.'Eighteen points'.'Nineteen cents'.'Twenty minutes'.'Twenty-one points'.'Twenty-two points'.'Twenty-three points'.'Twenty-four minutes'.'Twenty-five minutes'.'Twenty-six points'.'Twenty-seven points'.'Twenty-eight points'.'Twenty-nine points'.'Thirty minutes'.'Thirty-one points'.'Thirty-two points'.'Thirty-three points'.'Thirty-four points'.'Thirty-five minutes'.'Thirty-six minutes'.'Thirty-seven minutes'.'Thirty-eight points'.'Thirty-nine points'.'Forty minutes'.'Forty-one points'.'Forty-two points'.'Forty-three points'.'Forty-four points'.'Forty-five'.'Forty-six minutes'.'Forty-seven minutes'.'Forty-eight points'.'Forty-nine.'.'Fifty points'.'Fifty-one points'.'Fifty-two points'.'Fifty-three points'.'Fifty-four points'.'Fifty-five minutes'.'Fifty-six minutes'.'Fifty-seven points'.'Fifty-eight points'.'Fifty-nine.'].// Box size
      boxSize: {
        seconds: 580.minutes: 440}}}}</script>

Copy the code

Achieve compass rotation

The new code is as follows:

<template>
  <div class="home">
    <!-- 分 -->
    <div class="box-wrapper">
      <div class="circle-box" :style="boxStyle('minutes', minutesDeg)">
        <span
          v-for="(item, index) in minuteTexts"
          :key="item"
          :style="spanStyle(boxSize.minutes, minuteTexts, index)"
          :class="{'active': index === currentMinutes}"
        >{{ item }}</span>
      </div>
    </div>
    <! - seconds -- -- >
    <div class="box-wrapper">
      <div class="circle-box" :style="boxStyle('seconds', secondsDeg)">
        <span
          v-for="(item, index) in secondTexts"
          :key="item"
          :style="spanStyle(boxSize.seconds, secondTexts, index)"
          :class="{'active': index === currentSeconds}"
        >{{ item }}</span>
      </div>
    </div>
  </div>
</template>

<script>

export default {
  name: 'Home'.data() {
    return {
      currentMinutes: 0.// Current - minute
      currentSeconds: 0.// Current - second
      minutesDeg: 0.// Current - minute - rotation Angle
      secondsDeg: 0.// Current - face - rotation Angle
      timer: null / / timer}},mounted() {
    this.init()
  },

  methods: {
    init() {
      const d = new Date(a)const minutes = d.getMinutes() / / points
      const seconds = d.getSeconds() / / SEC.
      // The current time
      this.currentMinutes = minutes
      this.currentSeconds = seconds
      / / Angle
      this.minutesDeg = this.currentMinutes * this.getPerDeg(this.minuteTexts)
      this.secondsDeg = this.currentSeconds * this.getPerDeg(this.secondTexts)
      // Set the timer
      this.timer = setInterval(() = > {
        this.runClock()
      }, 1000)
      // Remember to clear the timer
      this.$once('hook:beforeDestroy'.() = > {
        clearInterval(this.timer)
      })
    },

    boxStyle(key, deg) {
      return {
        // Set the width and height of the text box
        width: this.boxSize[key] + 'px'.height: this.boxSize[key] + 'px'.// Add rotation
        transform: `rotate(-${deg}deg)`}},// The average degree of element spacing
    getPerDeg(texts) {
      return 360 / texts.length
    },

    runClock() {
      const d = new Date(a)const minutes = d.getMinutes() / / points
      const seconds = d.getSeconds() / / SEC.
      if (this.currentMinutes ! == minutes) {this.currentMinutes = minutes
        this.minutesDeg += this.getPerDeg(this.minuteTexts)
      }
      this.currentSeconds = seconds
      this.secondsDeg += this.getPerDeg(this.secondTexts)
    }
  }
}
</script>

<style lang="scss" scoped>
.circle-box {
  position: relative; // Add animation effectstransition: transform 0.4 sease-in-out; } // The text color is white when activated.circle-box span.active {
  color: #fff;
}
</style>

Copy the code

At this point, the whole work is almost complete, the rest of the month, day, time and other latitude is to follow the pattern of things, here will not be posted, interested students can go to GitHub to see the full code.

Full code address: GitHub

At the end

That’s the end of this article, thanks for reading, code words are not easy, welcome your likes 👍!!

If there is something wrong with this article, also welcome to comment area to correct, exchange!