Skip to content

Commit 01b416a

Browse files
committed
feat: init project
1 parent 2456573 commit 01b416a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+6333
-2
lines changed

README.md

Lines changed: 136 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,136 @@
1-
# vue-carousel
2-
一个简单、灵活的 Vue3 走马灯组件,非常轻量,只有 5kB。
1+
# Vue DevUI Carousel
2+
3+
一个简单、灵活的`Vue3`走马灯组件,非常轻量,只有`5kB`
4+
5+
预览地址:
6+
[https://kagol.gitee.io/vue-carousel/](https://kagol.gitee.io/vue-carousel/)
7+
8+
## 快速开始
9+
10+
创建一个vite工程:
11+
12+
```
13+
yarn create vite vite-demo --template vue-ts
14+
```
15+
16+
安装`Carousel`
17+
```
18+
yarn add vue-devui-carousel
19+
```
20+
21+
`main.ts`中引入`Carousel`
22+
```
23+
import Carousel from 'vue-devui-carousel'
24+
import 'vue-devui-carousel/dist/style.css'
25+
26+
createApp(App)
27+
.use(Carousel)
28+
.mount('#app')
29+
```
30+
31+
`App.vue`中使用:
32+
33+
```
34+
<DCarousel>
35+
<div class="carousel-item">page 1</div>
36+
<div class="carousel-item">page 2</div>
37+
<div class="carousel-item">page 3</div>
38+
</DCarousel>
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+
### DCarousel 组件
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+
### DCarouselIndicator 组件
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+
### DCarouselPrev 组件
119+
120+
插槽
121+
122+
| 属性 | 类型 | 默认 | 说明 |
123+
| ------- | ------ | ---- | -------------- |
124+
| default | -- | -- | 可选,默认插槽 |
125+
126+
### DCarouselNext 组件
127+
128+
插槽
129+
130+
| 属性 | 类型 | 默认 | 说明 |
131+
| ------- | ------ | ---- | -------------- |
132+
| default | -- | -- | 可选,默认插槽 |
133+
134+
参考:
135+
136+
[用积木理论设计的Carousel组件都有哪些有趣的玩法?](https://juejin.cn/post/7056193763810476063/)

carousel/__tests__/carousel.spec.ts

Whitespace-only changes.

carousel/index.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { App } from 'vue'
2+
import DCarousel from './src/carousel'
3+
import DCarouselIndicator from './src/components/carousel-indicator'
4+
import DCarouselPrev from './src/components/carousel-prev'
5+
import DCarouselNext from './src/components/carousel-next'
6+
import usePage from './src/composables/use-page'
7+
8+
export { DCarousel, DCarouselIndicator, DCarouselPrev, DCarouselNext, usePage }
9+
10+
export default {
11+
install(app: App) {
12+
app.component(DCarousel.name, DCarousel)
13+
app.component(DCarouselIndicator.name, DCarouselIndicator)
14+
app.component(DCarouselPrev.name, DCarouselPrev)
15+
app.component(DCarouselNext.name, DCarouselNext)
16+
app.config.globalProperties.usePage = usePage
17+
}
18+
}

carousel/src/carousel.scss

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
.devui-carousel {
2+
position: relative;
3+
overflow: hidden;
4+
5+
.devui-carousel-indicator {
6+
position: absolute;
7+
}
8+
}
9+
10+
.devui-carousel-item-container {
11+
display: flex;
12+
position: relative;
13+
transition: left 500ms ease 0s; // 内容切换时的动效
14+
15+
& > * {
16+
flex: 1;
17+
}
18+
}
19+
20+
.devui-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(--devui-highlight-overlay, rgba(255, 255, 255, .8));
29+
box-shadow: var(--devui-shadow-length-hover, 0 4px 16px 0) var(--devui-light-shadow, rgba(0, 0, 0, .1));
30+
display: inline-flex;
31+
align-items: center;
32+
justify-content: center;
33+
transition: background-color var(--devui-animation-duration-slow, .3s) var(--devui-animation-ease-in-out-smooth, cubic-bezier(.645, .045, .355, 1));
34+
35+
&:hover {
36+
background: var(--devui-area, #f8f8f8);
37+
}
38+
}

carousel/src/carousel.tsx

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { defineComponent, renderSlot, useSlots, watch, toRefs, ref } from 'vue'
2+
3+
// Components
4+
import DCarouselIndicator from './components/carousel-indicator'
5+
import DCarouselPrev from './components/carousel-prev'
6+
import DCarouselNext from './components/carousel-next'
7+
8+
// Composables
9+
import usePage from './composables/use-page'
10+
import useAutoplay from './composables/use-autoplay'
11+
12+
// Util
13+
import { formatPageIndex } from './carousel.util'
14+
15+
// Props/Types
16+
import { carouselProps, CarouselProps } from './carousel.type'
17+
18+
// SCSS
19+
import './carousel.scss'
20+
21+
export default defineComponent({
22+
name: 'DCarousel',
23+
components: {
24+
DCarouselIndicator,
25+
DCarouselPrev,
26+
DCarouselNext,
27+
},
28+
props: carouselProps,
29+
emits: ['update:modelValue'],
30+
setup(props: CarouselProps, { slots, emit }) {
31+
const { modelValue, autoplay, interval } = toRefs(props)
32+
33+
const { pageIndex, prevPage, nextPage, setPageIndex } = usePage(modelValue.value)
34+
const { startPlay, stopPlay } = useAutoplay(nextPage, interval.value)
35+
36+
const count = useSlots().default().filter(item => typeof item.type !== 'symbol').length
37+
const defaultFormattedPageIndex = formatPageIndex(pageIndex.value, count)
38+
const formattedPageIndex = ref(defaultFormattedPageIndex)
39+
40+
const launchTimer = (autoplay) => {
41+
if (autoplay) {
42+
startPlay()
43+
} else {
44+
stopPlay()
45+
}
46+
}
47+
48+
launchTimer(autoplay.value)
49+
50+
watch(autoplay, (newVal) => {
51+
launchTimer(newVal)
52+
})
53+
54+
watch(modelValue, (newVal: number) => {
55+
pageIndex.value = newVal
56+
})
57+
58+
watch(pageIndex, (newVal: number) => {
59+
emit('update:modelValue', newVal)
60+
formattedPageIndex.value = formatPageIndex(pageIndex.value, count)
61+
})
62+
63+
watch(formattedPageIndex, (newVal: number) => {
64+
pageIndex.value = newVal
65+
})
66+
67+
return () => {
68+
return (
69+
<div class="devui-carousel">
70+
<div
71+
class="devui-carousel-item-container"
72+
style={{
73+
width: count * 100 + '%',
74+
left: -(formattedPageIndex.value - 1) * 100 + '%',
75+
}}
76+
>
77+
{renderSlot(useSlots(), 'default')}
78+
</div>
79+
{
80+
slots.pagination
81+
? renderSlot(useSlots(), 'pagination', {
82+
prevPage, nextPage
83+
}) : <>
84+
<DCarouselPrev onClick={() => {
85+
emit('update:modelValue', props.modelValue-1)
86+
prevPage()
87+
}} />
88+
<DCarouselNext onClick={() => {
89+
emit('update:modelValue', props.modelValue+1)
90+
nextPage()
91+
}} />
92+
</>
93+
}
94+
{slots.indicator ? (
95+
slots.indicator({
96+
count,
97+
pageIndex: formattedPageIndex.value,
98+
setPageIndex
99+
})
100+
) : (
101+
<DCarouselIndicator
102+
count={count}
103+
v-model={formattedPageIndex.value}
104+
></DCarouselIndicator>
105+
)}
106+
</div>
107+
)
108+
}
109+
},
110+
})

carousel/src/carousel.type.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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<typeof carouselProps>

carousel/src/carousel.util.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
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+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
export default () => (
2+
<svg
3+
_ngcontent-jai-c250=""
4+
width="18px"
5+
height="18px"
6+
viewBox="0 0 16 16"
7+
version="1.1"
8+
>
9+
<g
10+
_ngcontent-jai-c250=""
11+
stroke="none"
12+
stroke-width="1"
13+
fill="none"
14+
fill-rule="evenodd"
15+
>
16+
<polygon
17+
_ngcontent-jai-c250=""
18+
fill="#293040"
19+
fill-rule="nonzero"
20+
points="10.7071068 12.2928932 9.29289322 13.7071068 3.58578644 8 9.29289322 2.29289322 10.7071068 3.70710678 6.41421356 8"
21+
></polygon>
22+
</g>
23+
</svg>
24+
)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export default () => (
2+
<svg
3+
_ngcontent-jai-c250=""
4+
width="18px"
5+
height="18px"
6+
viewBox="0 0 16 16"
7+
version="1.1"
8+
>
9+
<g
10+
_ngcontent-jai-c250=""
11+
stroke="none"
12+
stroke-width="1"
13+
fill="none"
14+
fill-rule="evenodd"
15+
>
16+
<polygon
17+
_ngcontent-jai-c250=""
18+
fill="#293040"
19+
fill-rule="nonzero"
20+
transform="translate(8.146447, 8.000000) scale(-1, 1) translate(-8.146447, -8.000000) "
21+
points="11.7071068 12.2928932 10.2928932 13.7071068 4.58578644 8 10.2928932 2.29289322 11.7071068 3.70710678 7.41421356 8"
22+
></polygon>
23+
</g>
24+
</svg>
25+
)

0 commit comments

Comments
 (0)