├── .github └── workflows │ └── auto-deploy.yml ├── .gitignore ├── .vscode └── extensions.json ├── LICENSE ├── README.md ├── carousel ├── __tests__ │ └── carousel.spec.ts ├── index.ts └── src │ ├── carousel.scss │ ├── carousel.tsx │ ├── carousel.type.ts │ ├── carousel.util.ts │ ├── components │ ├── arrow-left.tsx │ ├── arrow-right.tsx │ ├── carousel-indicator.scss │ ├── carousel-indicator.tsx │ ├── carousel-next.scss │ ├── carousel-next.tsx │ ├── carousel-prev.scss │ └── carousel-prev.tsx │ └── composables │ ├── use-autoplay.ts │ └── use-page.ts ├── docs ├── .vitepress │ ├── config.mts │ └── theme │ │ ├── index.scss │ │ ├── index.ts │ │ └── register-components.js ├── features │ ├── autoplay │ │ └── index.md │ ├── basic │ │ └── index.md │ ├── bilibili-events │ │ └── index.md │ ├── collapse-card │ │ └── index.md │ ├── custom-indicator │ │ └── index.md │ ├── custom-pagination │ │ └── index.md │ ├── huawei-events │ │ └── index.md │ ├── indicator-position │ │ └── index.md │ ├── juejin-events │ │ └── index.md │ ├── leetcode-card │ │ └── index.md │ ├── pagination-position │ │ └── index.md │ └── qqmusic │ │ └── index.md ├── index.md ├── public │ └── assets │ │ ├── juejin1.png │ │ └── juejin2.png └── vite.config.ts ├── index.html ├── package-lock.json ├── package.json ├── public ├── 1-default.gif ├── 10-collapse-card.gif ├── 2-juejin.gif ├── 3-indicator-position.gif ├── 4-custom-indicator.gif ├── 5-pagination-position.gif ├── 6-custom-pagination.gif ├── 7-huawei.gif ├── 8-qqmusic.gif ├── 9-bilibili.gif └── favicon.ico ├── src ├── App.vue ├── assets │ └── logo.png ├── components │ └── HelloWorld.vue ├── env.d.ts └── main.ts ├── tsconfig.json ├── vite.config.lib.ts └── vite.config.ts /.github/workflows/auto-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | permissions: 8 | contents: read 9 | pages: write 10 | id-token: write 11 | 12 | concurrency: 13 | group: pages 14 | cancel-in-progress: false 15 | 16 | jobs: 17 | # Build job 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | - name: Setup Node 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: 20 29 | cache: npm 30 | - name: Setup Pages 31 | uses: actions/configure-pages@v4 32 | - name: Install dependencies 33 | run: npm i 34 | - name: Build with VitePress 35 | run: npm run docs:build 36 | - name: Upload artifact 37 | uses: actions/upload-pages-artifact@v3 38 | with: 39 | path: docs/.vitepress/dist 40 | 41 | # Deployment job 42 | deploy: 43 | environment: 44 | name: github-pages 45 | url: ${{ steps.deployment.outputs.page_url }} 46 | needs: build 47 | runs-on: ubuntu-latest 48 | name: Deploy 49 | steps: 50 | - name: Deploy to GitHub Pages 51 | id: deployment 52 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | cache 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["johnsoncodehk.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Kagol 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue Carousel 2 | 3 | 一个简单、灵活的`Vue3`走马灯组件,非常轻量,只有`5kB`。 4 | 5 | 预览地址: 6 | [https://kagol.github.io/vue-carousel/](https://kagol.github.io/vue-carousel/) 7 | 8 | ## 快速开始 9 | 10 | 创建一个vite工程: 11 | 12 | ``` 13 | npm create vite vite-demo --template vue-ts 14 | ``` 15 | 16 | 安装`Carousel`: 17 | ``` 18 | npm install @kagol/vue-carousel 19 | ``` 20 | 21 | 在`main.ts`中引入`Carousel`: 22 | ``` 23 | import Carousel from '@kagol/vue-carousel' 24 | import '@kagol/vue-carousel/dist/style.css' 25 | 26 | createApp(App) 27 | .use(Carousel) 28 | .mount('#app') 29 | ``` 30 | 31 | 在`App.vue`中使用: 32 | 33 | ``` 34 | 35 | 36 | 37 | 38 | 39 | ``` 40 | 41 | ## 效果动图 42 | 43 | 默认效果: 44 | 45 | ![1-default.gif](https://pic.imgdb.cn/item/61f2b4932ab3f51d9107f30c.gif) 46 | 47 | 掘金活动: 48 | 49 | ![2-juejin.gif](https://pic.imgdb.cn/item/61f2b4932ab3f51d9107f315.gif) 50 | 51 | 指示器位置: 52 | 53 | ![3-indicator-position.gif](https://pic.imgdb.cn/item/61f2b4932ab3f51d9107f31c.gif) 54 | 55 | 自定义指示器: 56 | 57 | ![4-custom-indicator.gif](https://pic.imgdb.cn/item/61f2b4932ab3f51d9107f325.gif) 58 | 59 | 分页器位置: 60 | 61 | ![5-pagination-position.gif](https://pic.imgdb.cn/item/61f2b4932ab3f51d9107f32f.gif) 62 | 63 | 自定义分页器: 64 | 65 | ![6-custom-pagination.gif](https://pic.imgdb.cn/item/61f2b5282ab3f51d9108b5ef.gif) 66 | 67 | 华为官网: 68 | 69 | ![7-huawei.gif](https://pic.imgdb.cn/item/61f2b7bb2ab3f51d910d4651.gif) 70 | 71 | QQ音乐: 72 | 73 | ![8-qqmusic.gif](https://pic.imgdb.cn/item/61f2bb5d2ab3f51d91146170.gif) 74 | 75 | B站: 76 | 77 | ![9-bilibili.gif](https://pic.imgdb.cn/item/61f2b85e2ab3f51d910e65ab.gif) 78 | 79 | 手风琴式折叠卡片: 80 | 81 | ![10-collapse-card.gif](https://pic.imgdb.cn/item/61f2b6f42ab3f51d910bc018.gif) 82 | 83 | ## API 84 | 85 | ### Carousel 组件 86 | 87 | props 88 | 89 | | 属性 | 类型 | 默认 | 说明 | 90 | | ------- | ------ | ---- | -------------- | 91 | | v-model | Number | 1 | 可选,当前页码 | 92 | | autoplay | Boolean | true | 可选,是否自动播放 | 93 | | interval | Number | 3000 | 可选,自动播放的时间间隔,单位是毫秒 | 94 | 95 | 插槽 96 | 97 | | 属性 | 类型 | 默认 | 说明 | 98 | | ------- | ------ | ---- | -------------- | 99 | | default | -- | -- | 必选,默认插槽 | 100 | | indicator | -- | -- | 可选,指示器插槽 | 101 | | pagination | -- | -- | 可选,分页器插槽 | 102 | 103 | ### CarouselIndicator 组件 104 | 105 | props 106 | 107 | | 属性 | 类型 | 默认 | 说明 | 108 | | ------- | ------ | ---- | -------------- | 109 | | v-model | Number | 1 | 可选,当前页码 | 110 | | count | Number | -- | 可选,指示器元素数量 | 111 | 112 | 插槽 113 | 114 | | 属性 | 类型 | 默认 | 说明 | 115 | | ------- | ------ | ---- | -------------- | 116 | | default | ({ pageIndex, setPageIndex }) => {} | -- | 可选,默认插槽 | 117 | 118 | ### CarouselPrev 组件 119 | 120 | 插槽 121 | 122 | | 属性 | 类型 | 默认 | 说明 | 123 | | ------- | ------ | ---- | -------------- | 124 | | default | -- | -- | 可选,默认插槽 | 125 | 126 | ### CarouselNext 组件 127 | 128 | 插槽 129 | 130 | | 属性 | 类型 | 默认 | 说明 | 131 | | ------- | ------ | ---- | -------------- | 132 | | default | -- | -- | 可选,默认插槽 | 133 | 134 | 参考: 135 | 136 | [用积木理论设计的Carousel组件都有哪些有趣的玩法?](https://juejin.cn/post/7056193763810476063/) 137 | -------------------------------------------------------------------------------- /carousel/__tests__/carousel.spec.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kagol/vue-carousel/bad1b2ade8b317312af96a89d3ba2ce6ed5c4c88/carousel/__tests__/carousel.spec.ts -------------------------------------------------------------------------------- /carousel/index.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue' 2 | // import { usePage } from 'vueuse-components' 3 | import usePage from './src/composables/use-page' 4 | import Carousel from './src/carousel' 5 | import CarouselIndicator from './src/components/carousel-indicator' 6 | import CarouselPrev from './src/components/carousel-prev' 7 | import CarouselNext from './src/components/carousel-next' 8 | 9 | export { Carousel, CarouselIndicator, CarouselPrev, CarouselNext, usePage } 10 | 11 | export default { 12 | install(app: App) { 13 | app.component(Carousel.name, Carousel) 14 | app.component(CarouselIndicator.name, CarouselIndicator) 15 | app.component(CarouselPrev.name, CarouselPrev) 16 | app.component(CarouselNext.name, CarouselNext) 17 | app.config.globalProperties.usePage = usePage 18 | } 19 | } -------------------------------------------------------------------------------- /carousel/src/carousel.scss: -------------------------------------------------------------------------------- 1 | .xui-carousel { 2 | position: relative; 3 | overflow: hidden; 4 | 5 | .xui-carousel-indicator { 6 | position: absolute; 7 | } 8 | } 9 | 10 | .xui-carousel-item-container { 11 | display: flex; 12 | position: relative; 13 | transition: left 500ms ease 0s; // 内容切换时的动效 14 | 15 | & > * { 16 | flex: 1; 17 | } 18 | } 19 | 20 | .xui-arrow { 21 | position: absolute; 22 | top: 50%; 23 | margin-top: -18px; 24 | cursor: pointer; 25 | width: 36px; 26 | height: 36px; 27 | border-radius: 18px; 28 | background: var(--xui-highlight-overlay, rgba(255, 255, 255, .8)); 29 | box-shadow: var(--xui-shadow-length-hover, 0 4px 16px 0) var(--xui-light-shadow, rgba(0, 0, 0, .1)); 30 | display: inline-flex; 31 | align-items: center; 32 | justify-content: center; 33 | transition: background-color var(--xui-animation-duration-slow, .3s) var(--xui-animation-ease-in-out-smooth, cubic-bezier(.645, .045, .355, 1)); 34 | 35 | &:hover { 36 | background: var(--xui-area, #f8f8f8); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /carousel/src/carousel.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, renderSlot, useSlots, watch, toRefs, ref } from 'vue' 2 | // import { usePage } from 'vueuse-components' 3 | import usePage from './composables/use-page' 4 | 5 | // Components 6 | import CarouselIndicator from './components/carousel-indicator' 7 | import CarouselPrev from './components/carousel-prev' 8 | import CarouselNext from './components/carousel-next' 9 | 10 | // Composables 11 | import useAutoplay from './composables/use-autoplay' 12 | 13 | // Util 14 | import { formatPageIndex } from './carousel.util' 15 | 16 | // Props/Types 17 | import { carouselProps, CarouselProps } from './carousel.type' 18 | 19 | // SCSS 20 | import './carousel.scss' 21 | 22 | export default defineComponent({ 23 | name: 'Carousel', 24 | components: { 25 | CarouselIndicator, 26 | CarouselPrev, 27 | CarouselNext, 28 | }, 29 | props: carouselProps, 30 | emits: ['update:modelValue'], 31 | setup(props: CarouselProps, { slots, emit }) { 32 | const { modelValue, autoplay, interval } = toRefs(props) 33 | 34 | const { pageIndex, prevPage, nextPage, setPageIndex } = usePage(modelValue.value) 35 | const { startPlay, stopPlay } = useAutoplay(nextPage, interval.value) 36 | 37 | const getCarouselItems = () => { 38 | let carouselItems = [] 39 | useSlots().default().forEach(item => { 40 | if (typeof item.type !== 'symbol') { 41 | carouselItems.push(item) 42 | } else { 43 | if (Symbol.keyFor(item.type) === 'v-fgt') { 44 | carouselItems = [...carouselItems, ...item.children] 45 | } else if(Symbol.keyFor(item.type) === 'v-txt') { 46 | console.warn('不支持文本节点,需要包裹一层元素标签,比如:item 改成
item
') 47 | } 48 | } 49 | }) 50 | return carouselItems 51 | } 52 | 53 | const count = getCarouselItems().length 54 | const defaultFormattedPageIndex = formatPageIndex(pageIndex.value, count) 55 | const formattedPageIndex = ref(defaultFormattedPageIndex) 56 | 57 | const launchTimer = (autoplay) => { 58 | if (autoplay) { 59 | startPlay() 60 | } else { 61 | stopPlay() 62 | } 63 | } 64 | 65 | launchTimer(autoplay.value) 66 | 67 | watch(autoplay, (newVal) => { 68 | launchTimer(newVal) 69 | }) 70 | 71 | watch(modelValue, (newVal: number) => { 72 | pageIndex.value = newVal 73 | }) 74 | 75 | watch(pageIndex, (newVal: number) => { 76 | emit('update:modelValue', newVal) 77 | formattedPageIndex.value = formatPageIndex(pageIndex.value, count) 78 | }) 79 | 80 | watch(formattedPageIndex, (newVal: number) => { 81 | pageIndex.value = newVal 82 | }) 83 | 84 | return () => { 85 | return ( 86 | 124 | ) 125 | } 126 | }, 127 | }) 128 | -------------------------------------------------------------------------------- /carousel/src/carousel.type.ts: -------------------------------------------------------------------------------- 1 | import { extractPropTypes } from 'vue' 2 | 3 | export const carouselProps = { 4 | modelValue: { 5 | type: Number, 6 | }, 7 | autoplay: { 8 | type: Boolean, 9 | default: true, 10 | }, 11 | interval: { 12 | type: Number, 13 | default: 3000, 14 | } 15 | } 16 | 17 | export type CarouselProps = extractPropTypes 18 | -------------------------------------------------------------------------------- /carousel/src/carousel.util.ts: -------------------------------------------------------------------------------- 1 | export const formatPageIndex = (current, count) => { 2 | if (current <= 0) { 3 | return current + count * (Math.floor(-current / count) + 1) 4 | } else { 5 | return current % count === 0 ? count : current % count 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /carousel/src/components/arrow-left.tsx: -------------------------------------------------------------------------------- 1 | export default () => ( 2 | 9 | 16 | 22 | 23 | 24 | ) 25 | -------------------------------------------------------------------------------- /carousel/src/components/arrow-right.tsx: -------------------------------------------------------------------------------- 1 | export default () => ( 2 | 9 | 16 | 23 | 24 | 25 | ) 26 | -------------------------------------------------------------------------------- /carousel/src/components/carousel-indicator.scss: -------------------------------------------------------------------------------- 1 | .xui-carousel-indicator { 2 | display: flex; 3 | position: relative; 4 | bottom: 12px; 5 | justify-content: center; 6 | width: 100%; 7 | 8 | .xui-carousel-indicator-item { 9 | cursor: pointer; 10 | width: 6px; 11 | height: 6px; 12 | border-radius: 3px; 13 | margin-right: 8px; 14 | background: var(--xui-icon-fill, #d3d5d9); 15 | 16 | &.active { 17 | width: 24px; 18 | background: var(--xui-list-item-active-bg, #5e7ce0); 19 | transition: all var(--xui-animation-duration-slow, .3s) var(--xui-animation-ease-in-smooth, cubic-bezier(.645, .045, .355, 1)); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /carousel/src/components/carousel-indicator.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, toRefs, watch } from 'vue' 2 | // import { usePage } from 'vueuse-components' 3 | import usePage from '../composables/use-page' 4 | import './carousel-indicator.scss' 5 | 6 | export default defineComponent({ 7 | name: 'CarouselIndicator', 8 | props: { 9 | modelValue: { 10 | type: Number, 11 | }, 12 | count: { 13 | type: Number, 14 | } 15 | }, 16 | emits: ['update:modelValue'], 17 | setup(props, { emit, slots }) { 18 | const { modelValue } = toRefs(props) 19 | const { pageIndex, setPageIndex } = usePage(modelValue.value) 20 | const indicatorArr = Array.from(new Array(props.count).keys()) 21 | 22 | watch(modelValue, (newVal: number) => { 23 | pageIndex.value = newVal 24 | }) 25 | 26 | watch(pageIndex, (newVal: number) => { 27 | emit('update:modelValue', newVal) 28 | }) 29 | 30 | return () => { 31 | return 43 | } 44 | } 45 | }) -------------------------------------------------------------------------------- /carousel/src/components/carousel-next.scss: -------------------------------------------------------------------------------- 1 | .xui-arrow-right { 2 | right: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /carousel/src/components/carousel-next.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, renderSlot } from 'vue' 2 | import DArrowRight from './arrow-right' 3 | import './carousel-next.scss' 4 | 5 | export default defineComponent({ 6 | name: 'CarouselNext', 7 | setup(props, { slots, attrs }) { 8 | return () => { 9 | return <> 10 | { 11 | slots.default 12 | ? renderSlot(slots, 'default') 13 | : <> 14 |
15 | 16 |
17 | 18 | } 19 | 20 | } 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /carousel/src/components/carousel-prev.scss: -------------------------------------------------------------------------------- 1 | .xui-arrow-left { 2 | left: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /carousel/src/components/carousel-prev.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, renderSlot } from 'vue' 2 | import DArrowLeft from './arrow-left' 3 | import './carousel-prev.scss' 4 | 5 | export default defineComponent({ 6 | name: 'CarouselPrev', 7 | setup(props, { slots, attrs }) { 8 | return () => { 9 | return <> 10 | { 11 | slots.default 12 | ? renderSlot(slots, 'default') 13 | : <> 14 |
15 | 16 |
17 | 18 | } 19 | 20 | } 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /carousel/src/composables/use-autoplay.ts: -------------------------------------------------------------------------------- 1 | import { onUnmounted } from 'vue' 2 | 3 | export default function useAutoplay(next, interval) { 4 | let timerId 5 | 6 | const startPlay = () => { 7 | stopPlay() 8 | 9 | timerId = setInterval(next, interval) 10 | } 11 | 12 | const stopPlay = () => { 13 | if (timerId) { 14 | clearInterval(timerId) 15 | timerId = null 16 | } 17 | } 18 | 19 | onUnmounted(() => { 20 | stopPlay() 21 | }) 22 | 23 | return { 24 | startPlay, stopPlay 25 | } 26 | } -------------------------------------------------------------------------------- /carousel/src/composables/use-page.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default function usePage(defaultPageIndex = 1) { 4 | const pageIndex = ref(defaultPageIndex) 5 | 6 | const setPageIndex = (current: number) => { 7 | pageIndex.value = current 8 | } 9 | 10 | const jumpPage = (page: number) => { 11 | pageIndex.value += page 12 | } 13 | 14 | const prevPage = () => jumpPage(-1) 15 | 16 | const nextPage = () => jumpPage(1) 17 | 18 | return { pageIndex, setPageIndex, jumpPage, prevPage, nextPage } 19 | } 20 | -------------------------------------------------------------------------------- /docs/.vitepress/config.mts: -------------------------------------------------------------------------------- 1 | import { demoBlockPlugin, demoblockVitePlugin } from 'vitepress-theme-demoblock' 2 | 3 | const sidebar = [ 4 | { text: "快速开始", link: "/" }, 5 | { 6 | text: "特性", items: [ 7 | { text: "基本用法", link: "/features/basic/" }, 8 | { text: "自动播放", link: "/features/autoplay/" }, 9 | { text: "掘金活动", link: "/features/juejin-events/" }, 10 | { text: "指示器位置", link: "/features/indicator-position/" }, 11 | { text: "自定义指示器", link: "/features/custom-indicator/" }, 12 | { text: "分页器位置", link: "/features/pagination-position/" }, 13 | { text: "自定义分页器", link: "/features/custom-pagination/" }, 14 | { text: "手风琴式折叠卡片", link: "/features/collapse-card/" }, 15 | { text: "华为", link: "/features/huawei-events/" }, 16 | { text: "B站", link: "/features/bilibili-events/" }, 17 | { text: "QQ音乐", link: "/features/qqmusic/" }, 18 | { text: "LeetCode", link: "/features/leetcode-card/" }, 19 | ] 20 | } 21 | ] 22 | 23 | const nav = [ 24 | { text: 'Github', link: 'https://github.com/kagol/vue-carousel' } 25 | ] 26 | 27 | const config = { 28 | base: '/vue-carousel/', 29 | title: 'Vue Carousel', 30 | head: [ 31 | ['link', { rel: 'icon', type: 'image/svg+xml', href: '/assets/logo.svg' }], 32 | ], 33 | themeConfig: { 34 | sidebar, 35 | nav, 36 | logo: '/assets/logo.svg', 37 | }, 38 | markdown: { 39 | config: (md) => { 40 | // 这里可以使用 markdown-it 插件,vitepress-theme-demoblock就是基于此开发的 41 | md.use(demoBlockPlugin, { 42 | cssPreprocessor: 'scss' 43 | }) 44 | } 45 | }, 46 | vite: { 47 | plugins: [demoblockVitePlugin()] 48 | } 49 | } 50 | 51 | export default config 52 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.scss: -------------------------------------------------------------------------------- 1 | .debug { 2 | display: none; 3 | } 4 | 5 | .demoblock-view { 6 | overflow: initial !important; 7 | } 8 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import Theme from 'vitepress/dist/client/theme-default/index' 2 | import Carousel from '../../../carousel' 3 | 4 | import { registerComponents } from './register-components.js' 5 | import 'vitepress-theme-demoblock/dist/theme/styles/index.css' 6 | import './index.scss' 7 | 8 | export default { 9 | ...Theme, 10 | enhanceApp({ app }) { 11 | app.use(Carousel) 12 | registerComponents(app) 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/register-components.js: -------------------------------------------------------------------------------- 1 | import Demo from 'vitepress-theme-demoblock/dist/client/components/Demo.vue' 2 | import DemoBlock from 'vitepress-theme-demoblock/dist/client/components/DemoBlock.vue' 3 | export function registerComponents(app) { 4 | app.component('Demo', Demo) 5 | app.component('DemoBlock', DemoBlock) 6 | } 7 | -------------------------------------------------------------------------------- /docs/features/autoplay/index.md: -------------------------------------------------------------------------------- 1 | # 自动播放 2 | 3 | `autoplay`可以设置是否需要自动播放,默认值为`true`,默认每隔3000毫秒切换一次,可以通过设置`interval`自定义自动播放的间隔时间。 4 | 5 | :::demo 6 | 7 | 8 | ```vue 9 | 18 | 38 | 45 | ``` 46 | 47 | ::: 48 | -------------------------------------------------------------------------------- /docs/features/basic/index.md: -------------------------------------------------------------------------------- 1 | # 基本用法 2 | 3 | 4 | `Carousel`组件提供了默认插槽,直接将元素放在``即可实现轮播效果。 5 | 6 | :::demo 7 | 8 | 9 | ```vue 10 | 17 | 24 | ``` 25 | 26 | ::: 27 | -------------------------------------------------------------------------------- /docs/features/bilibili-events/index.md: -------------------------------------------------------------------------------- 1 | 2 | ### B站 3 | 4 | :::demo 5 | 6 | ```vue 7 | 80 | 177 | ``` 178 | 179 | ::: -------------------------------------------------------------------------------- /docs/features/collapse-card/index.md: -------------------------------------------------------------------------------- 1 | ### 手风琴式折叠卡片 2 | 3 | `CarouselIndicator`指示器组件提供了默认插槽,可以单独使用它实现自定义指示器效果,比如手风琴式折叠卡片。 4 | 5 | :::demo 6 | 7 | ```vue 8 | 31 | 85 | ``` 86 | 87 | ::: -------------------------------------------------------------------------------- /docs/features/custom-indicator/index.md: -------------------------------------------------------------------------------- 1 | # 自定义指示器 2 | 3 | 如果`Carousel`内置的`CarouselIndicator`指示器不满足你的要求,还可以定制自己的指示器。 4 | 5 | :::demo 6 | 7 | ```vue 8 | 25 | 36 | 64 | ``` 65 | 66 | ::: -------------------------------------------------------------------------------- /docs/features/custom-pagination/index.md: -------------------------------------------------------------------------------- 1 | ### 自定义分页器 2 | 3 | :::demo 4 | 5 | ```vue 6 | 23 | 52 | ``` 53 | 54 | ::: -------------------------------------------------------------------------------- /docs/features/huawei-events/index.md: -------------------------------------------------------------------------------- 1 | ### 华为 2 | 3 | :::demo 4 | 5 | ```vue 6 | 33 | 102 | ``` 103 | 104 | ::: -------------------------------------------------------------------------------- /docs/features/indicator-position/index.md: -------------------------------------------------------------------------------- 1 | # 指示器位置 2 | 3 | ``组件将其中的指示器子组件暴露出来,并提供了`indicator`插槽,因此可以随意调整`CarouselIndicator`的位置,比如放在左下角。 4 | 5 | :::demo 6 | 7 | ```vue 8 | 18 | 25 | ``` 26 | 27 | ::: -------------------------------------------------------------------------------- /docs/features/juejin-events/index.md: -------------------------------------------------------------------------------- 1 | # 掘金活动 2 | 3 | ``元素里面可以放任意元素,比如放上两张图片就是掘金活动的效果。 4 | 5 | :::demo 6 | 7 | ```vue 8 | 20 | ``` 21 | 22 | ::: 23 | -------------------------------------------------------------------------------- /docs/features/leetcode-card/index.md: -------------------------------------------------------------------------------- 1 | # LeetCode 2 | 3 | ### 首页轮播 4 | 5 | `Carousel`和`CarouselIndicator`组合使用,可以很方便地实现 LeetCode 的卡片轮播效果。 6 | 7 | :::demo 8 | 9 | ```vue 10 | 23 | 53 | ``` 54 | 55 | ::: 56 | 57 | ### 求职页轮播 58 | 59 | :::demo 60 | 61 | ```vue 62 | 75 | 105 | ``` 106 | 107 | ::: 108 | 109 | ### 题库页 110 | 111 | :::demo 112 | 113 | ```vue 114 | 173 | 187 | 254 | ``` 255 | 256 | ::: 257 | -------------------------------------------------------------------------------- /docs/features/pagination-position/index.md: -------------------------------------------------------------------------------- 1 | ### 分页器位置 2 | 3 | :::demo 4 | 5 | ```vue 6 | 17 | 24 | ``` 25 | 26 | ::: -------------------------------------------------------------------------------- /docs/features/qqmusic/index.md: -------------------------------------------------------------------------------- 1 | # QQ音乐 2 | 3 | :::demo 4 | 5 | ```vue 6 | 39 | 40 | 143 | ``` 144 | 145 | ::: 146 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # 快速开始 2 | 3 | 创建一个vite工程: 4 | 5 | ``` 6 | yarn create vite vite-demo --template vue-ts 7 | ``` 8 | 9 | 安装`Carousel`: 10 | ``` 11 | yarn add @kagol/vue-carousel 12 | ``` 13 | 14 | 在`main.ts`中引入`Carousel`: 15 | ``` 16 | import Carousel from '@kagol/vue-carousel' 17 | import '@kagol/vue-carousel/dist/style.css' 18 | 19 | createApp(App) 20 | .use(Carousel) 21 | .mount('#app') 22 | ``` 23 | 24 | 在`App.vue`中使用: 25 | 26 | ```vue 27 | 34 | 41 | ``` 42 | 43 | ### API 44 | 45 |
46 | 47 | #### Carousel 组件 48 | 49 | props 50 | 51 | | 属性 | 类型 | 默认 | 说明 | 52 | | ------- | ------ | ---- | -------------- | 53 | | v-model | Number | 1 | 可选,当前页码 | 54 | | autoplay | Boolean | true | 可选,是否自动播放 | 55 | | interval | Number | 3000 | 可选,自动播放的时间间隔,单位是毫秒 | 56 | 57 | 插槽 58 | 59 | | 属性 | 类型 | 默认 | 说明 | 60 | | ------- | ------ | ---- | -------------- | 61 | | default | -- | -- | 必选,默认插槽 | 62 | | indicator | -- | -- | 可选,指示器插槽 | 63 | | pagination | -- | -- | 可选,分页器插槽 | 64 | 65 | #### CarouselIndicator 组件 66 | 67 | props 68 | 69 | | 属性 | 类型 | 默认 | 说明 | 70 | | ------- | ------ | ---- | -------------- | 71 | | v-model | Number | 1 | 可选,当前页码 | 72 | | count | Number | -- | 可选,指示器元素数量 | 73 | 74 | 插槽 75 | 76 | | 属性 | 类型 | 默认 | 说明 | 77 | | ------- | ------ | ---- | -------------- | 78 | | default | ({ pageIndex, setPageIndex }) => {} | -- | 可选,默认插槽 | 79 | 80 | #### CarouselPrev 组件 81 | 82 | 插槽 83 | 84 | | 属性 | 类型 | 默认 | 说明 | 85 | | ------- | ------ | ---- | -------------- | 86 | | default | -- | -- | 可选,默认插槽 | 87 | 88 | #### CarouselNext 组件 89 | 90 | 插槽 91 | 92 | | 属性 | 类型 | 默认 | 说明 | 93 | | ------- | ------ | ---- | -------------- | 94 | | default | -- | -- | 可选,默认插槽 | 95 | -------------------------------------------------------------------------------- /docs/public/assets/juejin1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kagol/vue-carousel/bad1b2ade8b317312af96a89d3ba2ce6ed5c4c88/docs/public/assets/juejin1.png -------------------------------------------------------------------------------- /docs/public/assets/juejin2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kagol/vue-carousel/bad1b2ade8b317312af96a89d3ba2ce6ed5c4c88/docs/public/assets/juejin2.png -------------------------------------------------------------------------------- /docs/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vueJsx from '@vitejs/plugin-vue-jsx' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vueJsx()], 7 | css: { 8 | preprocessorOptions: { 9 | scss: { 10 | charset: false 11 | } 12 | } 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kagol/vue-carousel", 3 | "version": "0.1.2", 4 | "description": "Vue3 Carousel: A simple and flexible carousel component for Vue3, extremely lightweight with only 5kB.", 5 | "homepage": "https://kagol.github.io/vue-carousel/", 6 | "files": [ 7 | "dist" 8 | ], 9 | "main": "./dist/vue-carousel.umd.js", 10 | "module": "./dist/vue-carousel.es.js", 11 | "scripts": { 12 | "dev": "vite", 13 | "build": "vue-tsc --noEmit && vite build", 14 | "build:lib": "vite build --config vite.config.lib.ts", 15 | "preview": "vite preview", 16 | "docs:dev": "vitepress dev docs", 17 | "docs:build": "vitepress build docs", 18 | "docs:preview": "vitepress preview docs", 19 | "register:components": "vitepress-rc", 20 | "test": "jest" 21 | }, 22 | "author": "Kagol ", 23 | "license": "MIT", 24 | "dependencies": { 25 | "vue": "^3.4.21", 26 | "vueuse-components": "^0.0.1" 27 | }, 28 | "devDependencies": { 29 | "@vitejs/plugin-vue": "^5.0.4", 30 | "@vitejs/plugin-vue-jsx": "^3.1.0", 31 | "@vue/test-utils": "^2.4.5", 32 | "jest": "^29.7.0", 33 | "sass": "^1.74.1", 34 | "typescript": "^5.4.4", 35 | "vite": "^5.2.8", 36 | "vitepress": "^1.0.2", 37 | "vitepress-theme-demoblock": "^3.0.7", 38 | "vue-tsc": "^2.0.11" 39 | }, 40 | "optionalDependencies": { 41 | "@rollup/rollup-linux-x64-gnu": "4.14.1", 42 | "@rollup/rollup-darwin-x64": "4.14.1" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /public/1-default.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kagol/vue-carousel/bad1b2ade8b317312af96a89d3ba2ce6ed5c4c88/public/1-default.gif -------------------------------------------------------------------------------- /public/10-collapse-card.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kagol/vue-carousel/bad1b2ade8b317312af96a89d3ba2ce6ed5c4c88/public/10-collapse-card.gif -------------------------------------------------------------------------------- /public/2-juejin.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kagol/vue-carousel/bad1b2ade8b317312af96a89d3ba2ce6ed5c4c88/public/2-juejin.gif -------------------------------------------------------------------------------- /public/3-indicator-position.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kagol/vue-carousel/bad1b2ade8b317312af96a89d3ba2ce6ed5c4c88/public/3-indicator-position.gif -------------------------------------------------------------------------------- /public/4-custom-indicator.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kagol/vue-carousel/bad1b2ade8b317312af96a89d3ba2ce6ed5c4c88/public/4-custom-indicator.gif -------------------------------------------------------------------------------- /public/5-pagination-position.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kagol/vue-carousel/bad1b2ade8b317312af96a89d3ba2ce6ed5c4c88/public/5-pagination-position.gif -------------------------------------------------------------------------------- /public/6-custom-pagination.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kagol/vue-carousel/bad1b2ade8b317312af96a89d3ba2ce6ed5c4c88/public/6-custom-pagination.gif -------------------------------------------------------------------------------- /public/7-huawei.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kagol/vue-carousel/bad1b2ade8b317312af96a89d3ba2ce6ed5c4c88/public/7-huawei.gif -------------------------------------------------------------------------------- /public/8-qqmusic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kagol/vue-carousel/bad1b2ade8b317312af96a89d3ba2ce6ed5c4c88/public/8-qqmusic.gif -------------------------------------------------------------------------------- /public/9-bilibili.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kagol/vue-carousel/bad1b2ade8b317312af96a89d3ba2ce6ed5c4c88/public/9-bilibili.gif -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kagol/vue-carousel/bad1b2ade8b317312af96a89d3ba2ce6ed5c4c88/public/favicon.ico -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 34 | 35 | 50 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kagol/vue-carousel/bad1b2ade8b317312af96a89d3ba2ce6ed5c4c88/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 35 | 36 | 53 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import { DefineComponent } from 'vue' 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 6 | const component: DefineComponent<{}, {}, any> 7 | export default component 8 | } 9 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | // import Carousel from '../dist/vue-carousel.es' 4 | // import '../dist/style.css' 5 | import Carousel from '@kagol/vue-carousel' 6 | 7 | createApp(App) 8 | .use(Carousel) 9 | .mount('#app') 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "useDefineForClassFields": true, 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true, 12 | "lib": ["esnext", "dom"] 13 | }, 14 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] 15 | } 16 | -------------------------------------------------------------------------------- /vite.config.lib.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import vueJsx from '@vitejs/plugin-vue-jsx' 4 | import path from 'path' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | publicDir: false, 9 | build: { 10 | lib: { 11 | entry: path.resolve(__dirname, 'carousel/index.ts'), 12 | name: 'VueCarousel', 13 | fileName: (format) => `vue-carousel.${format}.js` 14 | }, 15 | rollupOptions: { 16 | // 确保外部化处理那些你不想打包进库的依赖 17 | external: ['vue'], 18 | output: { 19 | // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量 20 | globals: { 21 | vue: 'Vue' 22 | } 23 | } 24 | } 25 | }, 26 | plugins: [vue(), vueJsx()] 27 | }) 28 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import vueJsx from '@vitejs/plugin-vue-jsx' 4 | import path from 'path' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [vue(), vueJsx()], 9 | resolve: { 10 | alias: [ 11 | { find: '@kagol/vue-carousel', replacement: path.resolve(__dirname, 'carousel') } 12 | ] 13 | } 14 | }) 15 | --------------------------------------------------------------------------------