Vue.jsでマーキー(水平スクロール)コンポーネントを実装する方法

DOM操作とCSS Transformを使った基本的な実装

この方法は、テキストコンテンツをラップする要素のCSS transformプロパティを直接操作してスクロール効果を作成します。

テンプレート構造

<div class="marquee-container" ref="container">
  <div class="scrolling-content" ref="content">
    <p class="display-text">{{displayContent}}</p>
    <p class="measurement-text" ref="measurement"></p>
  </div>
  <p class="width-reference" ref="widthRef">{{displayContent}}</p>
</div>

コンポーネントロジック

export default {
  name: 'MarqueeComponent',
  props: {
    items: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      animationTimer: null,
      displayContent: '',
      currentPosition: 0
    };
  },
  created() {
    this.initializeContent();
  },
  mounted() {
    this.startScrolling();
  },
  methods: {
    initializeContent() {
      if (this.items.length > 0) {
        this.displayContent = this.items.join(' ');
      }
    },
    startScrolling() {
      const containerWidth = this.$refs.container.clientWidth;
      const textWidth = this.$refs.widthRef.scrollWidth;
      
      if (textWidth <= containerWidth) return;
      
      const measurementElement = this.$refs.measurement;
      measurementElement.textContent = this.displayContent;
      
      this.animationTimer = setInterval(() => {
        this.currentPosition -= 1;
        if (-this.currentPosition >= textWidth) {
          this.currentPosition = containerWidth;
        }
        this.$refs.content.style.transform = `translateX(${this.currentPosition}px)`;
      }, 20);
    }
  },
  beforeDestroy() {
    if (this.animationTimer) {
      clearInterval(this.animationTimer);
    }
  }
};

スタイル設定

.marquee-container {
  width: 100%;
  overflow: hidden;
  position: relative;
}

.scrolling-content {
  display: flex;
}

.display-text {
  margin-right: 0.16rem;
}

.width-reference {
  word-break: keep-all;
  white-space: nowrap;
  position: absolute;
  opacity: 0;
  top: 0;
}

p {
  word-break: keep-all;
  white-space: nowrap;
  font-size: 0.28rem;
}

requestAnimationFrameとCSSマージンを使ったアニメーション実装

このアプローチは、ブラウザのアニメーションAPIを活用してよりスムーズなスクロールを実現します。

テンプレート構造

<div class="animation-wrapper">
  <div class="scrolling-text" style="color: rgb(255, 121, 1); font-size: 16px;">
    <i class="el-icon-warning-outline" style="font-weight: 500;" />&nbsp;
    {{notificationText}}
  </div>
</div>

スクロール制御ロジック

methods: {
  startTextAnimation(scrollText) {
    const wrapper = document.querySelector(".animation-wrapper");
    const textElement = document.querySelector(".scrolling-text");
    
    textElement.textContent += scrollText;
    const textWidth = textElement.offsetWidth;
    let offset = wrapper.offsetWidth;
    
    const animate = () => {
      if (offset <= -textWidth) {
        textElement.style.marginLeft = '0px';
        offset = wrapper.offsetWidth;
      }
      textElement.style.marginLeft = `${offset--}px`;
      window.requestAnimationFrame(animate);
    };
    
    window.requestAnimationFrame(animate);
  }
}

コンテナスタイル

.animation-wrapper {
  display: flex;
  overflow: hidden;
  text-overflow: ellipsis;
  width: 100%;
}

.scrolling-text {
  padding: 0 5px;
  white-space: nowrap;
}

シームレスなループ表示のためのテキスト幅計算

動的にテキストコンテンツを変更する場合、正確な幅の計算が重要です。

methods: {
  calculateTextWidth(textContent, fontSize) {
    const doubleByteCount = (textContent.match(/[^\x00-\xff]/g) || []).length;
    const singleByteCount = textContent.length - doubleByteCount;
    const totalWidth = (doubleByteCount * 2 + singleByteCount) * fontSize / 2;
    return totalWidth + 5;
  }
}

このメソッドは、全角文字を2文字分、半角文字を1文字分として計算し、正確なピクセル幅を返します。

タグ: vue.js javascript CSS フロントエンド コンポーネント

6月23日 21:32 投稿