Search code examples
javascriptcssvue.jssassfrontend

Number scrolling animation (& content generation) using Vue and SCSS


I was looking for a reference for number scrolling animation and came across the following code (uses SCSS so will not run in the browser):

new Vue({
  el: ".app",
  data: {
    number: 0
  },
  methods: {
    setNumber(e) {
      this.number = e.target.value;
    }
  },
  computed: {
    numberString() {
      return String(this.number).padStart(this.total, "0");
    }
  },
  created() {
    this.total = 9;
  }
});
* {
  margin: 0;
  padding: 0;
}

$height: 80px;

html {
  height: 100%;
}

body {
  font: 16px/1.5 "Helvetica Neue", Helvetica, arial;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

.app {
  margin: auto;
}

.wrap {
  display: flex;
  margin: 0 0 20px;
  color: #fff;
  text-align: center;
  font-size: 40px;
}

.item {
  flex: 0 0 auto;
  margin: 0 10px 0 0;
  box-shadow: 0 0 6px #0b415b;
  position: relative;
  padding: 4px;
  border-radius: 4px;
  overflow: hidden;

  &:before {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background: linear-gradient(
      to bottom,
      #239cc8 49.9%,
      #1b6386 50%,
      #1b6386 100%
    );
  }

  &:last-child {
    margin: 0;
  }

  &__inner {
    position: relative;
    background: linear-gradient(
      to bottom,
      #3fb9d9 49.9%,
      #3596ae 50%,
      #3596ae 100%
    );
    width: 50px;
    overflow: hidden;
  }

  &__digit {
    transition: 0.3s transform;
    line-height: $height;
    height: $height;

    &-list {
      &:before {
        display: block;
        $content: "";
        @for $i from 0 through 9 {
          $content: #{$content}#{$i}#{"\A"}#{" "};
        }
        content: "#{$content}";
      }
    }
  }
}

.range {
  width: 100%;
  display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.8/vue.global.min.js"></script>
<div class="app">
  <div class="wrap">
    <div class="item" v-for="i in total" :key="i">
      <div class="item__inner">
        <div class="item__digit" :style="{
              transform: `translateY(${Number(numberString.charAt(i - 1)) * -100}%)`
            }">
          <div class="item__digit-list"></div>
        </div>
      </div>
    </div>
  </div>
  <input type="range" min="0" value="0" :max="10 ** total - 1" @input="setNumber" class="range" />
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

(Original Codepen: https://codepen.io/lpjc/pen/MWpMxYa)

I understand the fundamentals of Vue and Sass, but am really confused by 2 parts:

  1. HTML: Where does the 'i' come from in this part which binds the style?
<div class="item__digit" :style="{
              transform: `translateY(${Number(numberString.charAt(i - 1)) * -100}%)`}">
  1. SCSS: Does this part translates to displaying the changing number content? How? I understand that #{} is for interpolation and $var are variables.
&-list {
      &:before {
        display: block;
        $content: "";
        @for $i from 0 through 9 {
          $content: #{$content}#{$i}#{"\A"}#{" "};
        }
        content: "#{$content}";
      }
    }
  }
}

I tried to search the official documentations and also google for related topics, but was difficult to find related information explaining this specific case.


Solution

  • 1. The i comes from the Vue for loop.

    In Vue templates, loops and other logic can be defined with element properties, called Vue directives. A for loop can be written with the directive v-for. In the code in your question, the outermost element of the loop has v-for="i in total".

    <div class="item" v-for="i in total" :key="i">
        <div class="item__inner">
            <div class="item__digit" :style="{
                transform: `translateY(${Number(numberString.charAt(i - 1)) * -100}%)`
            }">
                <div class="item__digit-list"></div>
            </div>
        </div>
    </div>
    

    Now, inside that element, the i variable is available. In regular JavaScript that would be like writing

    for ( const i in total ) {
        // Can use the i variable in here
    }
    

    For more information on the v-for directive, see https://vuejs.org/guide/essentials/list

    2. The SASS is creating a list of numbers.

    If you go to https://www.sassmeister.com/ you can render the SCSS into CSS to see what it's doing. You'll see that the content is getting compiled to

    content: "0\a  1\a  2\a  3\a  4\a  5\a  6\a  7\a  8\a  9\a  ";
    

    (where \a is a newline character).

    In SASS, #{$i} will print out the value of $i into that string. Rather than typing out "0\a 1\a 2\a 3\a 4\a 5\a 6\a 7\a 8\a 9\a ", they chose to write a little @for loop to do the same thing. The @for loop counts from 0 to 9, and appends the current number to the string.

    In practice this creates a ::before element containing a vertical list of the numbers from 0 to 9. The element's parent hides overflowing content, so only one number is visible at a time.