const player = Vue.createApp({ data() { return { videoOgv: 'video.ogv', videoWebm: 'video.webm', commentJson: 'comments.json', comments: [], visibleComments: [], containerWidth: 0 }; }, methods: { async loadComments() { try { const resp = await fetch(this.commentJson); if (!resp.ok) { throw new Error(`HTTPエラー: ${resp.status}`); } const data = await resp.json(); this.comments = data.map((comment, index) => { return { ...comment, yPos: (index % 10) * 30 }; }); } catch (error) { console.error("コメントを受取に失敗:", error); } }, updateComments() { const currentTime = this.$refs.videoPlayer.currentTime; let yPos = 0; this.visibleComments = this.comments.filter(comment => { const isVisible = currentTime >= comment.timestamp && currentTime <= (comment.timestamp + 5); if (isVisible && typeof comment.yPos === 'undefined') { comment.yPos = yPos; yPos += 30; } return isVisible; }); this.$nextTick(() => { this.animateComments(); }); }, animateComments() { const commentsElements = this.$refs.videoContainer.querySelectorAll('.comment'); commentsElements.forEach(el => { const commentWidth = el.offsetWidth; let totalDistance = this.containerWidth + commentWidth; totalDistance -= (100-commentWidth); el.style.transition = 'transform 5s linear'; el.style.transform = `translateX(-${totalDistance}px)`; }); }, updateContainerWidth() { const container = this.$refs.videoContainer; this.containerWidth = container ? container.offsetWidth : 0; }, calculateCommentWidth(text) { const tempDiv = document.createElement("div"); tempDiv.style.position = "absolute"; tempDiv.style.whiteSpace = "nowrap"; tempDiv.style.fontSize = "50px"; tempDiv.textContent = text; document.body.appendChild(tempDiv); const width = tempDiv.clientWidth; document.body.removeChild(tempDiv); return width; }, createAnimationKeyframes() { const styleSheet = document.createElement("style"); styleSheet.type = "text/css"; styleSheet.innerHTML = ` @keyframes scrollComment { from { transform: translateX(${this.containerWidth}px); } to { transform: translateX(-${this.containerWidth}px); } } `; document.head.appendChild(styleSheet); }, pauseComments() { const commentsElements = this.$refs.videoContainer.querySelectorAll('.comment'); commentsElements.forEach(el => { const computedStyle = window.getComputedStyle(el); const transform = computedStyle.transform || computedStyle.webkitTransform; el.style.transform = transform; el.style.transition = 'none'; }); }, playComments() { this.animateComments(); }, attachVideoEventListeners() { const videoPlayer = this.$refs.videoPlayer; videoPlayer.addEventListener('play', this.playComments); videoPlayer.addEventListener('pause', this.pauseComments); } }, mounted() { this.loadComments(); this.createAnimationKeyframes(); window.addEventListener('resize', () => { this.updateContainerWidth(); this.createAnimationKeyframes(); }); this.updateContainerWidth(); this.attachVideoEventListeners(); window.addEventListener('resize', this.updateContainerWidth); }, beforeUnmount() { window.removeEventListener('resize', this.updateContainerWidth); }, computed: { commentAnimationStyle() { return ` `; } } }); player.mount("#root");