Biến ảnh sprites thành ảnh động với vuejs

Ảnh sprites là gì?

Sprites là một hình ảnh lớn được tạo ra bằng cách gộp nhiều ảnh nhỏ lại với nhau theo một cách được định trước sao cho có thể tái sử dụng lại từng ảnh nhỏ mà không bị ảnh hưởng bởi các ảnh khác.

Kiểu như thế này :

Hoặc các sticker của fb:

Mục tiêu của bài viết

Ưu điểm so với việc dùng ảnh gif

  1. Tôi ưu hơn performance web vì thay vì load ảnh gif thì thay vào đó là anh png.
  2. Có thể tương tác với ảnh (vd: có thể cho ảnh dừng lại hoặc tiếp tục chuyển động, hoặc chuyển động vs số lần nhất định rồi ngừng).
  3. Tùy chỉnh chuyển động nhanh chậm của ảnh.

Xây dựng component vuejs

Hướng giải quyết

Đơn giản chỉ là dùng js để thay đổi background-position qua từng phần của bước ảnh và lặp lại khi đến ảnh cuối vậy là mình đã có 1 ảnh tương tự gif.

Viết component

Khởi tạo các props và style cho component:

<template>
  <div :style="style">
  </div>
</template>

<script>
import { clearInterval } from 'timers';
export default {
  props: {
    image: String, // Url image
    height: Number, // chiều cao của component
    width: Number, // chiều rộng của component
    loop: Number, // số lần lặp của ảnh
    frame: Number, // số hình chạy qua trên 1 giây
    max: Number, // số hình của ảnh
    column: Number, // số cột
    row: Number, // số dòng
  },

  data(){
  // khởi tạo style ban đầu
    return {
      style: {
        width: this.width + 'px',
        height: this.height + 'px',
        background: `url(${this.image})`,
        backgroundSize: `${this.width * this.column}px ${this.height * this.row}px`,
        backgroundPosition: '0px 0px'
      }
    }
  },

Tiếp theo chúng ta viết function để ảnh có thể chuyển động.

 methods: {
    play() {
      let i = 0
      let position = {
        x: 0,
        y: 0,
        loop: 0,
      } // khởi tạo vị trí ban đầu
      const playTimer = setInterval(() => {
        i++;
        if(i % this.column) {
          position.x -= this.width; 
        } else {
          position.y -= this.height; 
          position.x = 0;
        }

        if (i == this.max) {
          i = 0
          position.y = 0; 
          position.x = 0;
          position.loop++;
          if(position.loop >= this.loop) {
            clearInterval(playTimer)
          }
        }
        
        this.$set(this.style, 'backgroundPosition', `${position.x}px ${position.y}px`) // set lại style
      }, 1000/this.frame);
    }
  }

Gọi hàm play()

mounted() {
    this.play()
  },

Vậy là chúng ta đã viết xong component để ảnh chuyện động. full code :

<template>
  <div :style="style">
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    image: String,
    height: Number,
    width: Number,
    loop: Number,
    frame: Number,
    max: Number,
    column: Number,
    row: Number,
  },

  mounted() {
    this.play()
  },

  data(){
    return {
      style: {
        width: this.width + 'px',
        height: this.height + 'px',
        background: `url(${this.image})`,
        backgroundSize: `${this.width * this.column}px ${this.height * this.row}px`,
        backgroundPosition: '0px 0px'
      }
    }
  },
  
  methods: {
    play() {
      let i = 0
      let position = {
        x: 0,
        y: 0,
        loop: 0,
      }
      const playTimer = setInterval(() => {
        i++;
        if(i % this.column) {
          position.x -= this.width; 
        } else {
          position.y -= this.height; 
          position.x = 0;
        }

        if (i == this.max) {
          i = 0
          position.y = 0; 
          position.x = 0;
          position.loop++;
          if(position.loop >= this.loop) {
            clearInterval(playTimer)
          }
        }
        
        this.$set(this.style, 'backgroundPosition', `${position.x}px ${position.y}px`)
      }, 1000/this.frame);
    }
  }
}
</script>

Việc tiếp theo là chúng ta gọi component này ra và sử dụng thôi.

Mình test thử với ảnh này nhé:

<template>
  <div id="app">
    <ImageGif
      image = "https://images.viblo.asia/a28b8126-1298-4ca7-a507-53865b32238d.png"
      :width="100"
      :height="100"
      :column="7"
      :row="4"
      :max="27"
      :frame="20"
      :loop="10"
    />
  </div>
</template>

<script>
import ImageGif from './components/image-gif.vue'

export default {
  name: 'app',
  components: {
    ImageGif
  }
}
</script>

Thành quả :

https://codesandbox.io/s/vue-template-znn3p

Kết luận

Các bạn cũng có thể tham khảo thêm github: https://github.com/tuananhp-1844/image-gif