You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue.js组件scrollTo is not a function错误:媒体无法横向滚动求助

解决Vue组件中媒体横向滚动按钮报错问题

问题原因

报错Uncaught TypeError: mediaItems.value.scrollTo is not a function的核心原因:

  • 你将mediaItems这个ref绑定在了v-for循环生成的.media-post-items元素上,Vue会自动把循环内的ref收集为数组,而非单个DOM元素。因此mediaItems.value是数组,不存在scrollTo方法。
  • 原scrollMedia函数没有区分目标帖子的媒体容器,点击按钮会错误地尝试操作所有帖子的容器,逻辑不符合需求。

解决方案

1. 调整滚动函数逻辑

移除全局的mediaItems ref,修改scrollMedia函数为接收目标DOM元素和滚动方向,直接操作指定容器:

const scrollMedia = (target: HTMLDivElement, direction: number) => {
  const scrollAmount = target.scrollLeft + direction * 220;
  target.scrollTo({
    left: scrollAmount,
    behavior: 'smooth',
  });
};

2. 修改模板中的点击事件

通过$event获取按钮的兄弟元素(即对应的媒体容器),传递给滚动函数:

<button class="post-nav-btn" @click="scrollMedia($event.currentTarget.nextElementSibling as HTMLDivElement, -1)">&#10094;</button>
<div class="media-post-items">
  <!-- 媒体内容 -->
</div>
<button class="post-nav-btn" @click="scrollMedia($event.currentTarget.previousElementSibling as HTMLDivElement, 1)">&#10095;</button>

3. 修复CSS布局问题

原.media-post的flex-wrap: wrap会导致按钮和媒体容器换行,调整样式确保布局正确:

.media-post {
  position: relative;
  display: flex;
  gap: 10px;
  margin-top: 10px;
  align-items: center; /* 按钮与媒体容器垂直居中 */
}

.media-post-items {
  flex: 1; /* 占据剩余空间 */
  display: flex;
  overflow-x: auto;
  scroll-behavior: smooth;
  gap: 10px;
  padding: 10px 0;
}

修改后的完整组件代码

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import axios from 'axios';
import { axiosConfig } from '@/config/axios';

const posts = ref<any[]>([]);

const fetchPosts = async () => {
    try {
        const response = await axios.get('/posts', axiosConfig);
        posts.value = response.data.posts.sort((a: any, b: any) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
    } catch (error) {
        console.error('Failed to fetch posts:', error);
    }
};

const isImage = (fileUrl: string): boolean => {
    const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg'];
    const fileExtension = fileUrl.split('.').pop()?.toLowerCase();
    return imageExtensions.includes(fileExtension || '');
};

const isVideo = (fileUrl: string): boolean => {
    const videoExtensions = ['mp4', 'webm', 'ogg', 'avi', 'mov', 'flv'];
    const fileExtension = fileUrl.split('.').pop()?.toLowerCase();
    return videoExtensions.includes(fileExtension || '');
};

const scrollMedia = (target: HTMLDivElement, direction: number) => {
    const scrollAmount = target.scrollLeft + direction * 220;
    target.scrollTo({
        left: scrollAmount,
        behavior: 'smooth',
    });
};

onMounted(() => {
    fetchPosts();
});
</script>

<template>
    <div class="post-container">
        <div class="post" v-for="post in posts" :key="post._id">
            <div class="media-post">
                <button class="post-nav-btn" @click="scrollMedia($event.currentTarget.nextElementSibling as HTMLDivElement, -1)">&#10094;</button>
                <div class="media-post-items">
                    <div v-for="(file, index) in post.uploaded" :key="index" class="media-post-item-wrapper">
                        <div class="media-post-item">
                            <img v-if="isImage(file.media)" :src="file.media" alt="Post media" />
                            <video v-else-if="isVideo(file.media)" :src="file.media" controls></video>
                        </div>
                    </div>
                </div>
                <button class="post-nav-btn" @click="scrollMedia($event.currentTarget.previousElementSibling as HTMLDivElement, 1)">&#10095;</button>
            </div>
        </div>
    </div>
</template>

<style scoped>
.post-container {
    overflow: auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    height: 80vh;
    width: 100%;
    position: fixed;
    top: 0;
    left: 0;
    margin-top: 80px;
}
.post {
    width: 600px;
    border: solid 1px white;
    padding: 10px;
    border-radius: 10px;
    margin-bottom: 20px;
}
.media-post {
    position: relative;
    display: flex;
    gap: 10px;
    margin-top: 10px;
    align-items: center;
}
.media-post img, .media-post video {
    height: 200px;
    border-radius: 10px;
    object-fit: contain;
}
.media-post-item {
    position: relative;
    display: inline-block;
    overflow: hidden;
    border-radius: 10px;
    min-height: 220px;
}
.post-nav-btn {
    background: rgba(0,0,0,0.5);
    color: white;
    border: none;
    border-radius: 50%;
    width: 40px;
    height: 40px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 18px;
}
.post-nav-btn:hover {
    background: rgba(0,0,0,0.7);
}
.media-post-items {
    flex: 1;
    display: flex;
    overflow-x: auto;
    scroll-behavior: smooth;
    gap: 10px;
    padding: 10px 0;
}
/* 隐藏滚动条(可选) */
.media-post-items::-webkit-scrollbar {
    display: none;
}
.media-post-items {
    -ms-overflow-style: none;
    scrollbar-width: none;
}
</style>

内容的提问来源于stack exchange,提问作者Alan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 08:04:58