Skip to content

仿抖音小程序短视频组件-基于uniapp+vue3+typescript

Notifications You must be signed in to change notification settings

akFace/uniapp-tiktok

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

基于 uniapp 开发的仿抖音小程序组件(超高性能)

GIF 截图效果

  • GIF 帧率不够,实际真机效果非常丝滑

注:组件使用 vue3+typescript 开发

  • 全局仅渲染 3 个 swiper-item
  • 实测,不管加载多少数据也能丝滑滚动
  • 适用于 vue3,vue2 请自行修
  • 自动预加载视频
  • 首次渲染优化

快速开始,下载插件后请按照此方法运行调试

  1. 安装 nodejs: https://nodejs.org/en/
  2. 安装依赖: npm i
  3. 运行项目: npm run dev:mp-weixin
  4. 构建项目资源: npm run build:mp-weixin
  5. 打开小程序开发工具导入dist/dev/mp-weixin 即可
  6. 真机预览,请点小程序开发工具上的预览,扫码真机预览即可

参考 API

属性 类型 默认值 说明
videoList Array - 视频列表,数组对象 {src: string, poster?: string, objectFit?: string}
autoObjectFit Boolean true 是否开启视频自动自适应平铺模式,竖屏全屏覆盖,横屏自适应居中, 此参数优先级低于 videoList 中的 objectFit
loop Boolean true 是否循环播放视频
controls Boolean false 显示原生控制栏
autoplay Boolean true 是否自动播放
autoChange Boolean false 是否自动滚动播放
loadMoreOffsetCount Number 2 滚动加载阈值(即播放到剩余多少个之后触发加载更多
@play EventHandle - 当开始/继续播放时触发 play 事件
@error EventHandle - 视频播放出错时触发
@ended EventHandle - 当播放到末尾时触发 ended 事件
@loadMore EventHandle - 当滚动到最后第 N 条数据后,需要加载更多时触发
@change EventHandle - 切换视频时触发
@click EventHandle - 点击整个视频区域触发
@controlstoggle EventHandle - 控制栏状态变化触发

Slots 插槽

属性 默认值 说明
default - 自定义内容,覆盖到视频上方的所有自定义内容 v-slot="data" 为当前渲染数据,请参照使用示例

方法

// 播放第几个视频
mTikTokRef.value?.initSwiperData(index);

// 播放与暂停
mTikTokRef.value?.togglePlay();

// 播放跳转到指定位置,单位 s
mTikTokRef.value?.playSeeked(8);

使用示例

<template>
  <div class="video-container">
    <mTikTok
      ref="mTikTokRef"
      :video-list="state.videoList"
      @loadMore="loadMore"
      @change="change"
    >
      <!-- 此处为用户完全自定义 data 中的数据为当前渲染的数据 -->
      <template v-slot="data">
        <!-- active修复视频悬浮层消失和点击的问题 -->
        <view
          class="video-side-right"
          :class="{ active: state.cutVideo.id === data.item.id }"
        >
          <view class="action-item action-item-user">
            <image
              class="shop-logo"
              src="https://examples-1251000004.cos.ap-shanghai.myqcloud.com/sample.jpeg?imageMogr2/crop/180x180/gravity/center"
            />
            <view class="action-btn">
              <text class="iconfont">+</text>
            </view>
            <text class="action-item-text"></text>
          </view>
          <view class="action-item">
            <text class="iconfont icon-star11beifen"></text>
            <text class="action-item-text">{{ data.item.id }}</text>
          </view>
          <view class="action-item">
            <text class="iconfont icon-share"></text>
            <text class="action-item-text">分享</text>
          </view>
        </view>
        <!-- active修复视频悬浮层消失和点击的问题 -->
        <view
          class="video-bottom-area"
          :class="{ active: state.cutVideo.id === data.item.id }"
        >
          <view class="shop-name"> @{{ data.item.name }} </view>
          <view class="shop-card">{{ data.item.desc }}</view>
        </view>
      </template>
    </mTikTok>
  </div>
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref } from "vue";
// 导入组件
import mTikTok from "@/components/mTikTok.vue";

const mTikTokRef = ref<InstanceType<typeof mTikTok>>();

const state = reactive({
  cutVideo: {} as AnyObject,
  videoList: [
    {
      src: "https://xjc.demo.hongcd.com/uploads/20230214/84e165388f5bfdb1550522f50f5a57bb.mp4",
      id: "1",
      name: "开玩笑的鸡毛",
      desc: "这里是简介内容",
    },
    {
      src: "https://xjc.demo.hongcd.com/uploads/20230214/3f26d950ac286eecedba49f5295f0819.mp4",
      id: "2",
      name: "开玩笑的鸡毛",
      desc: "这里是简介内容",
    },
    {
      src: "https://xjc.demo.hongcd.com/uploads/20230215/8b5ac0420fe61e2f9660d7b8af998f7b.mp4",
      id: "3",
      name: "开玩笑的鸡毛",
      desc: "这里是简介内容",
    },
    {
      src: "https://xjc.demo.hongcd.com/uploads/20210128/d932b2d78cebb0a8cb8f9a6216790dfb.mp4",
      id: "4",
      name: "开玩笑的鸡毛",
      desc: "这里是简介内容",
    },
    {
      src: "https://xjc.demo.hongcd.com/uploads/20210128/0c64cbeea28b10c06eee8728c762449e.mp4",
      id: "5",
      name: "开玩笑的鸡毛",
      desc: "这里是简介内容",
    },
    {
      src: "https://xjc.demo.hongcd.com/uploads/20210327/1b72e1b6153cd29df07f5449991e8083.mp4",
      id: "6",
      name: "开玩笑的鸡毛",
      desc: "这里是简介内容",
    },
    {
      src: "https://xjc.demo.hongcd.com/uploads/20230214/7e1a0baaebc4e656bbbfbc44d7a55a60.mp4",
      id: "7",
      name: "开玩笑的鸡毛",
      desc: "这里是简介内容",
    },
  ],
});

const loadMore = () => {
  // 触发加载更多
  console.log("加载更多");
};

const change = (e: any) => {
  state.cutVideo = e.detail;
  console.log("🚀 ~ file: index.vue:53 ~ change ~ data:", e);
};

// 播放第几个
const playIndex = (index: number) => {
  mTikTokRef.value?.initSwiperData(index);
};

onMounted(() => {
  // 直接播放第3个
  // playIndex(3);
});
</script>
<style lang="scss">
$zIndex: 99;

.video-layer {
  position: absolute;
  right: 12px;
  bottom: 120px;
  color: #fff;
}

.video-bottom-area {
  position: absolute;
  left: 20px;
  bottom: 40px;
  opacity: 0;
  transition: all 250ms;
  z-index: 0;

  &.active {
    opacity: 1;
    z-index: $zIndex;
    transition-delay: 200ms;
  }

  .shop-name {
    color: #fff;
    margin-bottom: 6px;
  }

  .shop-card {
    width: 160px;
    height: 80px;
    background-color: rgba(255, 255, 255, 0.5);
    border-radius: 4px;
  }
}

.video-side-right {
  position: absolute;
  right: 12px;
  bottom: 120px;
  color: #fff;
  opacity: 0;
  transition: all 250ms;
  z-index: 0;

  &.active {
    opacity: 1;
    z-index: $zIndex;
    transition-delay: 200ms;
  }

  .action-item {
    position: relative;
    margin-bottom: 20px;
    text-align: center;

    .shop-logo {
      width: 40px;
      height: 40px;
      border-radius: 50%;
      overflow: hidden;
    }

    .iconfont {
      display: block;
      font-size: 28px;
    }

    .action-item-text {
      display: block;
      font-size: 12px;
    }

    .action-btn {
      position: absolute;
      left: 50%;
      transform: translateX(-50%);
      bottom: -8px;
      width: 20px;
      height: 20px;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      background-color: #f60;

      .iconfont {
        font-size: 16px;
      }
    }
  }

  .action-item-user {
    margin-bottom: 40px;
  }
}
</style>