使用vue3+vite+typescript编写一个网易云首页轮播插件

vue3+vite已经成熟。就想着哪来练手项目,然后写到页面轮播部分时突然奇想,不如使用vue3编写一个轮播组件练练手,于是网上找到了轮播图的编写思路,同时参考element-plus库源码的编写思路,最后用render函数的形式完成了这个组件,今天分享给大家

效果图如下

在这里插入图片描述

具体实现过程

创建项目的过程忽略了,想要了解的稍后我放出git地址

编写Swiper组件

<!--
 * @Description: 轮播组件
 * @Autor: ZmSama
 * @Date: 2021-05-28 15:00:37
-->
<script lang="ts">
import { defineComponent, h, getCurrentInstance, onMounted, ref, VNode } from 'vue';
import Slider from './Slider.vue';
import throttle from '../utils/throttle';
export interface ISlider {
  sty?: Object;
  className?: string;
}
export default defineComponent({
  name: 'Swiper',
  components: {
    Slider,
  },
  props: {
    initial: {
      type: Number,
      default: 0,
    },
    interval: {
      type: Number,
      default: 5000,
    },
    auto: {
      type: Boolean,
      default: true,
    },
  },
  setup(props, ctx) {
    // 因为setup中没有this,使用方法得到实例
    const instance = getCurrentInstance();

    // 轮播子内容
    const childrenNode = ref([]);
    // 初始index
    const initial = ref(props.initial);
    // 间隔时间
    const interval = ref(props.interval);
    // 定时器对象
    let timer = null;

    // 自动轮播
    const autoPlay = () => {
      timer = setInterval(() => {
        initial.value++;
        if (initial.value >= childrenNode.value.length) {
          initial.value = 0;
        }
      }, interval.value);
    };

    // 上一页(节流一下)
    const preSlider = throttle(() => {
      initial.value--;
      if (initial.value < 0) {
        initial.value = childrenNode.value.length - 1;
      }
    });

    // 下一页(节流一下)
    const nextSlider = throttle(() => {
      initial.value++;
      if (initial.value >= childrenNode.value.length) {
        initial.value = 0;
      }
    });

    // 点击指示器前往某一页
    const gotoSlider = (i: number) => {
      initial.value = i;
    };

    // 鼠标进入
    const enterSlider = () => {
      clearInterval(timer);
    };

    // 鼠标离开
    const leaveSlider = () => {
      props.auto && autoPlay();
    };

    // 处理每一项的样式
    const computedSty = (index: number, arr: Array<ISlider>) => {
      // 确保索引合法
      let len = arr.length;
      // 判断初始值
      index < 0 ? 0 : index >= len ? len - 1 : initial;
      // 第一项
      let temp1 = index - 1;
      // 第二项(中间项)
      let temp2 = index;
      // 第三项
      let temp3 = index + 1;

      // 判断起始值处于哪里
      temp1 < 0 && (temp1 = len + temp1);
      temp3 >= len && (temp3 = temp3 - len);

      // 修改每一项的样式

      return arr.map((item: ISlider, index) => {
        // 初始样式
        let transform = 'translate(-50%, -50%) scale(0.55)',
          zIndex = 0,
          className = '';

        // 根据索引确定项目的样式
        switch (index) {
          case temp2:
            zIndex = 1;
            transform = 'translate(-50%, -50%) scale(1)';
            className = ' is-active';
            break;
          case temp1:
            zIndex = 0;
            transform = 'translate(-100%, -50%) scale(0.85)';
            className = '';
            break;
          case temp3:
            zIndex = 0;
            transform = 'translate(-0%, -50%) scale(0.85)';
            className = '';
            break;
        }
        // 给每一项绑定样式
        item.sty = {
          transform,
          zIndex,
        };
        //  加类
        item.className = className;

        return item;
      });
    };

    onMounted(() => {
      // 这里就是要插入外部传入内容的地方,instance.slots.default()返回的是一个数组,
      // 数组内就是所有未定义名字的插槽内容,
      // 如果想在render中获得相同的内容则调用this.$solts. default()
      const arr = instance.slots.default();
      // 从上面取出所有的实际子节点,同时处理v-for循环和直接写组件的形式,
      // 还要防止用户使用非slider组件(使用v-for得到的将是数组、直接写就是对象)
      const Collection = Array.from(arr).map((item: VNode) => {
        // 说明是v-for指令的
        if (typeof item.type == 'symbol') {
          return item.children;
        } else if (item.type['name'] == 'Slider') {
          return item;
        } else {
          throw new Error('swiper组件内部只允许使用<slider></slider>组件');
        }
      });
      // 将上面得到的可能是二维数组打散成一维数组既是所有的实际子节点
      childrenNode.value = Collection.flat().map((item: VNode) => item.children);

      props.auto && autoPlay();
    });
    return {
      initial,
      computedSty,
      childrenNode,
      preSlider,
      nextSlider,
      gotoSlider,
      enterSlider,
      leaveSlider,
    };
  },
  render() {
    const {
      initial,
      computedSty,
      childrenNode,
      preSlider,
      nextSlider,
      gotoSlider,
      enterSlider,
      leaveSlider,
    } = this;
    // 轮播主体内容

    const sliderList = computedSty(initial, childrenNode);
    //  渲染轮播内容
    const slider = h(
      'div',
      {
        class: 'slider-content',
      },
      [
        sliderList.map((item, index) => {
          return h(
            'div',
            {
              class: {
                slider: true,
                'is-active': index === initial,
              },
              style: item.sty,
              onClick: () => gotoSlider(index),
              onMouseenter: () => enterSlider(),
              onMouseleave: () => leaveSlider(),
            },
            item.default()
          );
        }),
      ]
    );

    // // 底部点点
    let dotItem = childrenNode.map((item, index) => {
      return h('div', {
        class: {
          dot: true,
          'is-active': index == initial,
        },
        onClick: () => throttle(gotoSlider(index)),
      });
    });
    const dot = h(
      'div',
      {
        class: 'dot-wrap',
      },
      [dotItem]
    );

    // // 左右按钮
    const arrowLeft = h('div', {
      class: 'arrow-left',
      onClick: () => preSlider(),
      onMouseenter: () => enterSlider(),
      onMouseleave: () => leaveSlider(),
    });

    const arrowRight = h('div', {
      class: 'arrow-right',
      onClick: () => nextSlider(),
      onMouseenter: () => enterSlider(),
      onMouseleave: () => leaveSlider(),
    });

    //组合
    return h(
      'div',
      {
        class: 'slider-container',
      },
      [slider, dot, arrowLeft, arrowRight]
    );
  },
});
</script>
<style lang="scss" scoped></style>

接着写Slider组件

<!--
 * @Description: 
 * @Autor: ZmSama
 * @Date: 2021-05-28 15:00:52
-->
<template>
  <div>
    <slot>Slider</slot>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'Slider',
});
</script>
<style lang="scss" scoped></style>

接下来是样式

// 系统颜色
$red: rgba(255, 0, 0, 0.816);

// 垂直和水平居中
@mixin jcc-aic {
  display: flex;
  justify-content: center;
  align-items: center;
}
// 水平居中
@mixin jcc {
  display: flex;
  justify-content: center;
}
// 垂直居中
@mixin aic {
  display: flex;
  align-items: center;
}

// 横向排列,从头开始
@mixin jcs-row {
  display: flex;
  justify-content: flex-start;
  flex-direction: row;
}
// 横向排列,从头开始,垂直居中
@mixin jcc-aic-row {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  flex-direction: row;
}
// 轮播样式
.slider-container {
  height: 100%;
  width: 100%;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  position: absolute;
  .slider-content {
    position: relative;
    box-sizing: border-box;
    height: 100%;
    width: 100%;
    .slider {
      position: absolute;
      width: 55%;
      height: 82%;
      background-color: aqua;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      transition: all 0.3s ease-in-out;
      cursor: pointer;
      border-radius: 14px;
      overflow: hidden;
      box-shadow: 3px 3px 3px 1px rgba(0, 0, 0, 0.2);
      @include jcc-aic;
      img {
        width: 100%;
        height: 100%;
      }
    }
  }
  .dot-wrap {
    position: absolute;
    bottom: 0;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 2;
    @include jcc-aic-row;
    .dot {
      width: 10px;
      height: 10px;
      border-radius: 50%;
      background-color: #ccc;
      margin-left: 10px;
      transition: all 0.3s ease-in-out;
      cursor: pointer;
    }
    .is-active {
      background-color: $red;
    }
  }

  .arrow-left {
    position: absolute;
    left: 10px;
    top: 50%;
    margin-top: -15px;
    cursor: pointer;
    border: solid #ffff;
    border-width: 0 3px 3px 0;
    display: inline-block;
    transform: rotate(135deg);
    padding: 10px;
    opacity: 0;
  }
  .arrow-right {
    position: absolute;
    right: 10px;
    top: 50%;
    margin-top: -15px;
    cursor: pointer;
    border: solid #ffff;
    border-width: 0 3px 3px 0;
    display: inline-block;
    transform: rotate(-45deg);
    padding: 10px;
    opacity: 0;
  }
  &:hover .arrow-left {
    opacity: 1;
  }
  &:hover .arrow-right {
    opacity: 1;
  }
}

最后是具体使用(这些图片是从网易云的官网复制下来的,可能有天就失效了,自己找图片换上去

<!--
 * @Description: 
 * @Autor: ZmSama
 * @Date: 2021-05-28 14:40:41
-->
<template>
  <div class="app">
    <div class="swiper-container">
      <swiper :initial="2" :interval="3000" :auto="true">
        <slider name="slider2" v-for="item in source" :key="item.id">
          <img :src="item.pic" alt="" srcset="" />
        </slider>
      </swiper>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
  name: 'App',
  setup() {
    const source = [
      {
        id: 1,
        label: 'Slider1',
        color: '#ff3399',
        pic: 'http://p1.music.126.net/O5hmcHHdJpABcArdHOXXZw==/109951166006223055.jpg?imageView&quality=89',
      },
      {
        id: 2,
        label: 'Slider2',
        color: '#99ffff',
        pic: 'http://p1.music.126.net/uhxClMS2xY2b48a-PsoG9g==/109951166006290269.jpg?imageView&quality=89',
      },
      {
        id: 3,
        label: 'Slider3',
        color: '#66cc00',
        pic: 'http://p1.music.126.net/Rgqg8zqkKiewTUMjh0GMCg==/109951166005126541.jpg?imageView&quality=89',
      },
      {
        id: 4,
        label: 'Slider4',
        color: '#0066ff',
        pic: 'http://p1.music.126.net/oMXHYkRHy5XbE0905ljqZg==/109951166005813356.jpg?imageView&quality=89',
      },
      {
        id: 5,
        label: 'Slider5',
        color: '#00ff33',
        pic: 'http://p1.music.126.net/IXXt2TECvZIeEVNUr1E_gA==/109951166004785770.jpg?imageView&quality=89',
      },
    ];
    return {
      source,
    };
  },
});
</script>

<style lang="scss" scoped>
.app {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  .swiper-container {
    width: 80%;
    height: 300px;
    position: relative;
  }
}
</style>

以上就是全部的源代码了

懒得自己写的朋友可以访问我的git地址。如果能点个start就更好了项目地址

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
可以使用以下步骤在 Vue3 + Vite3 + Typescript使用 WangEditor 编辑器: 1. 安装 WangEditor。可以使用 npm 或 yarn 安装。 ```bash npm install wangeditor --save # 或者 yarn add wangeditor ``` 2. 在 `main.ts` 中引入 WangEditor 和 CSS 文件。 ```typescript import WangEditor from 'wangeditor'; import 'wangeditor/release/wangEditor.css'; const app = createApp(App); app.config.globalProperties.$WangEditor = WangEditor; // 挂载编辑器到全局 app.mount('#app'); ``` 3. 在组件使用 WangEditor。 ```vue <template> <div class="editor-wrapper"> <div ref="editorRef"></div> </div> </template> <script lang="ts"> import { defineComponent, onMounted, ref } from 'vue'; export default defineComponent({ name: 'Editor', setup() { const editorRef = ref<HTMLDivElement>(); onMounted(() => { const editor = new (window as any).$WangEditor(editorRef.value); editor.create(); }); return { editorRef, }; }, }); </script> <style lang="scss"> .editor-wrapper { height: 400px; .w-e-text-container { height: 100%; } } </style> ``` 在 `onMounted` 钩子函数中,使用 `new (window as any).$WangEditor` 来创建编辑器实例,并传入编辑器容器的 DOM 节点。调用 `editor.create()` 方法来创建编辑器。 注意:由于 WangEditor 的类型定义文件并不完善,因此可以在 `tsconfig.json` 中添加以下配置来避免类型检查报错。 ```json { "compilerOptions": { "skipLibCheck": true } } ``` 这样,就可以在 Vue3 + Vite3 + Typescript使用 WangEditor 编辑器了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值