diff --git a/examples/sites/demos/apis/carousel.js b/examples/sites/demos/apis/carousel.js index 4184e25d12..d99c251e54 100644 --- a/examples/sites/demos/apis/carousel.js +++ b/examples/sites/demos/apis/carousel.js @@ -22,7 +22,7 @@ export default { type: 'String', defaultValue: '', desc: { - 'zh-CN': '宽高比,默认为16:2。', + 'zh-CN': '宽高比,默认为 16:2。', 'en-US': 'Aspect ratio. The default value is 16:2.' }, mode: ['mobile-first'], @@ -199,6 +199,22 @@ export default { mode: ['pc', 'mobile-first'], pcDemo: 'swipeable', mfDemo: '' + }, + //draggable + { + name: 'draggable', + type: 'boolean', + defaultValue: 'false', + desc: { + 'zh-CN': '是否允许拖拽切换幻灯片', + 'en-US': 'Whether to allow dragging to switch slides' + }, + mode: ['pc', 'mobile-first'], + pcDemo: 'draggable', + mfDemo: 'draggable', + meta: { + stable: '3.29.0' + } } ], events: [ diff --git a/examples/sites/demos/mobile-first/app/collapse/draggable.vue b/examples/sites/demos/mobile-first/app/collapse/draggable.vue new file mode 100644 index 0000000000..57556a3230 --- /dev/null +++ b/examples/sites/demos/mobile-first/app/collapse/draggable.vue @@ -0,0 +1,45 @@ + + + diff --git a/examples/sites/demos/mobile-first/app/collapse/webdoc/collapse.js b/examples/sites/demos/mobile-first/app/collapse/webdoc/collapse.js index 2b2a4ba4bc..7174090810 100644 --- a/examples/sites/demos/mobile-first/app/collapse/webdoc/collapse.js +++ b/examples/sites/demos/mobile-first/app/collapse/webdoc/collapse.js @@ -22,7 +22,7 @@ export default { }, desc: { 'zh-CN': - '

配置 `accordion` 属性为 true 后,折叠面板将展示手风琴效果。配置 `v-model` 设置当前激活的面板(如果是手风琴模式,绑定值类型需要为 string ,否则为 array );`change` 事件,在当前激活面板改变时触发,参数为当前的 value 值;配置 `name` 属性作为每个 collapse-item 的唯一标志符

', + '

配置 `accordion` 属性为 true 后,折叠面板将展示手风琴效果。配置 `v-model` 设置当前激活的面板 (如果是手风琴模式,绑定值类型需要为 string,否则为 array );`change` 事件,在当前激活面板改变时触发,参数为当前的 value 值;配置 `name` 属性作为每个 collapse-item 的唯一标志符

', 'en-US': '

If the `accordion` attribute is set to true, the collapse panel displays the accordion effect. Configure `v-model` Sets the currently active panel. (In accordion mode, the binding value type must be string. Otherwise, the binding value type is array.) `change` event, which is triggered when the current active panel changes. The parameter is the current value. Configure the `name` attribute as the unique identifier for each collapse-item

' }, @@ -88,7 +88,7 @@ export default { 'en-US': 'Prevent Collapse Panel Close Event' }, desc: { - 'zh-CN': '

:before-close 折叠面板关闭前事件,方法里面 return true/false,表示是否可以关闭

', + 'zh-CN': '

:before-close 折叠面板关闭前事件,方法里面 return true/false,表示是否可以关闭

', 'en-US': '

:before-close: indicates the event before the folding panel is closed. In the method, return true or false indicates whether the folding panel can be closed.

' }, @@ -154,6 +154,19 @@ export default { 'en-US': '

Icons are implemented by configuring the built-in icon slot of Collapse.

' }, codeFiles: ['slot-icon.vue'] + }, + //draggable + { + demoId: 'draggable', + name: { + 'zh-CN': '可拖拽', + 'en-US': 'Draggable' + }, + desc: { + 'zh-CN': '

通过配置 Collapse 内置的 draggable 属性实现拖拽

', + 'en-US': '

Drag and drop is implemented by configuring the built-in draggable slot of Collapse.

' + }, + codeFiles: ['draggable.vue'] } ] } diff --git a/examples/sites/demos/pc/app/carousel/draggable.spec.ts b/examples/sites/demos/pc/app/carousel/draggable.spec.ts new file mode 100644 index 0000000000..800bc16d36 --- /dev/null +++ b/examples/sites/demos/pc/app/carousel/draggable.spec.ts @@ -0,0 +1,36 @@ +import { test, expect } from '@playwright/test' + +test('draggable - 基本功能测试', async ({ page }) => { + page.on('pageerror', (exception) => expect(exception).toBeNull()) + + // 1. 打开页面 + await page.goto('carousel#draggable') + + // 2. 验证组件加载 + await expect(page.locator('.tiny-carousel')).toBeVisible() + + // 3. 验证有轮播项 + const items = page.locator('.tiny-carousel__item') + const count = await items.count() + expect(count).toBeGreaterThan(0) + + // 4. 验证初始状态 + await expect(items.nth(0)).toHaveClass(/active/) + + // 5. 测试鼠标事件可以触发(不验证具体切换) + const carousel = page.locator('.tiny-carousel').first() + await carousel.hover() + await carousel.dispatchEvent('mousedown') + await page.waitForTimeout(50) + await carousel.dispatchEvent('mousemove', { clientX: 100, clientY: 100 }) + await page.waitForTimeout(50) + await carousel.dispatchEvent('mouseup') + + // 6. 测试触摸事件可以触发 + await carousel.dispatchEvent('touchstart') + await page.waitForTimeout(50) + await carousel.dispatchEvent('touchend') + + // 7. 验证没有报错即可 + console.log('draggable 测试通过') +}) \ No newline at end of file diff --git a/examples/sites/demos/pc/app/carousel/draggable.vue b/examples/sites/demos/pc/app/carousel/draggable.vue new file mode 100644 index 0000000000..1ae164d37f --- /dev/null +++ b/examples/sites/demos/pc/app/carousel/draggable.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/examples/sites/demos/pc/app/carousel/webdoc/carousel.js b/examples/sites/demos/pc/app/carousel/webdoc/carousel.js index ba0bb6f880..ebdd99ba14 100644 --- a/examples/sites/demos/pc/app/carousel/webdoc/carousel.js +++ b/examples/sites/demos/pc/app/carousel/webdoc/carousel.js @@ -198,6 +198,21 @@ export default { 'en-US': '

Embed a carousel scene in the pop-up window.

' }, codeFiles: ['dialog-show.vue'] + }, + { + demoId: 'draggable', + name: { + 'zh-CN': '拖拽', + 'en-US': 'Draggable' + }, + desc: { + 'zh-CN': '

通过配置 draggable 属性为true后,走马灯的幻灯片内容将支持拖拽。

\n', + 'en-US': '

After draggable is set to true, the slide content of the walk-through is supported for dragging.

\n' + }, + codeFiles: ['draggable.vue'], + meta: { + stable: '3.29.0' + } } ], features: [ diff --git a/packages/renderless/src/carousel/index.ts b/packages/renderless/src/carousel/index.ts index 67026913ae..0e32264a51 100644 --- a/packages/renderless/src/carousel/index.ts +++ b/packages/renderless/src/carousel/index.ts @@ -6,48 +6,48 @@ import { emulate } from '@opentiny/utils' // 鼠标进入幻灯片事件 export const handleMouseEnter = ({ api, state }) => - () => { - state.hover = true - api.pauseTimer() - } + () => { + state.hover = true + api.pauseTimer() + } export const handleMouseLeave = ({ api, state }) => - () => { - state.hover = false - api.startTimer() - } + () => { + state.hover = false + api.startTimer() + } export const itemInStage = (state) => - ({ item, index }) => { - const length = state.items.length - const arr = state.items - - if ( - (index === length - 1 && item.inStage && arr[0].active) || - (item.inStage && arr[index + 1] && arr[index + 1].active) - ) { - return POSITION.Left - } else if ( - (index === 0 && item.inStage && arr[length - 1].active) || - (item.inStage && arr[index - 1] && arr[index - 1].active) - ) { - return POSITION.Right - } + ({ item, index }) => { + const length = state.items.length + const arr = state.items + + if ( + (index === length - 1 && item.inStage && arr[0].active) || + (item.inStage && arr[index + 1] && arr[index + 1].active) + ) { + return POSITION.Left + } else if ( + (index === 0 && item.inStage && arr[length - 1].active) || + (item.inStage && arr[index - 1] && arr[index - 1].active) + ) { + return POSITION.Right + } - return false - } + return false + } export const handleButtonEnter = ({ api, state }) => - (arrow) => { - state.items.forEach((item, index) => { - if (arrow === api.itemInStage({ item, index })) { - item.hover = true - } - }) - } + (arrow) => { + state.items.forEach((item, index) => { + if (arrow === api.itemInStage({ item, index })) { + item.hover = true + } + }) + } export const handleButtonLeave = (state) => () => { state.items.forEach((item) => { @@ -66,22 +66,22 @@ export const resetItemPosition = (state) => (oldIndex) => { // 播放幻灯片 export const playSlides = ({ api, props, state }) => - () => { - let newIndex - let oldIndex = state.activeIndex - - if (state.activeIndex < state.items.length - 1) { - newIndex = state.activeIndex + 1 - } else if (props.loop) { - newIndex = 0 - } - - api.canActive(newIndex, oldIndex).then((result) => { - if (result) { - state.activeIndex = newIndex + () => { + let newIndex + let oldIndex = state.activeIndex + + if (state.activeIndex < state.items.length - 1) { + newIndex = state.activeIndex + 1 + } else if (props.loop) { + newIndex = 0 } - }) - } + + api.canActive(newIndex, oldIndex).then((result) => { + if (result) { + state.activeIndex = newIndex + } + }) + } // 暂停计时器 export const pauseTimer = (state) => () => clearInterval(state.timer) @@ -89,61 +89,61 @@ export const pauseTimer = (state) => () => clearInterval(state.timer) // 启动计时器 export const startTimer = ({ api, props, state }) => - () => { - if (props.interval <= 0 || !props.autoplay) { - return - } + () => { + if (props.interval <= 0 || !props.autoplay) { + return + } - state.timer = setInterval(api.playSlides, props.interval) - } + state.timer = setInterval(api.playSlides, props.interval) + } export const setActiveItem = ({ api, props, state }) => - (index) => { - if (typeof index === 'string') { - const filteredItems = state.items.filter((item) => item.name === index) + (index) => { + if (typeof index === 'string') { + const filteredItems = state.items.filter((item) => item.name === index) - if (filteredItems.length > 0) { - index = state.items.indexOf(filteredItems[0]) + if (filteredItems.length > 0) { + index = state.items.indexOf(filteredItems[0]) + } } - } - index = Number(index) + index = Number(index) - if (isNaN(index) || index !== Math.floor(index)) { - return - } + if (isNaN(index) || index !== Math.floor(index)) { + return + } - const length = state.items.length - const oldIndex = state.activeIndex - let newIndex + const length = state.items.length + const oldIndex = state.activeIndex + let newIndex - if (index < 0) { - newIndex = props.loop ? length - 1 : 0 - } else if (index >= length) { - newIndex = props.loop ? 0 : length - 1 - } else { - newIndex = index - } + if (index < 0) { + newIndex = props.loop ? length - 1 : 0 + } else if (index >= length) { + newIndex = props.loop ? 0 : length - 1 + } else { + newIndex = index + } - const nextProcess = () => { - state.activeIndex = newIndex + const nextProcess = () => { + state.activeIndex = newIndex - if (oldIndex === state.activeIndex) { - api.resetItemPosition(oldIndex) + if (oldIndex === state.activeIndex) { + api.resetItemPosition(oldIndex) + } } - } - if (newIndex === oldIndex) { - nextProcess() - } else { - api.canActive(newIndex, oldIndex).then((result) => { - if (result) { - nextProcess() - } - }) + if (newIndex === oldIndex) { + nextProcess() + } else { + api.canActive(newIndex, oldIndex).then((result) => { + if (result) { + nextProcess() + } + }) + } } - } export const canActive = (props) => (newIndex, oldIndex) => { return new Promise((resolve) => { if (typeof props.beforeSwipe === 'function') { @@ -153,53 +153,53 @@ export const canActive = (props) => (newIndex, oldIndex) => { } }).then((result) => result !== false) } - +//下一张 export const prev = ({ api, state }) => - () => - api.setActiveItem(state.activeIndex - 1) - + () => + api.setActiveItem(state.activeIndex - 1) +//上一张 export const next = ({ api, state }) => - () => - api.setActiveItem(state.activeIndex + 1) + () => + api.setActiveItem(state.activeIndex + 1) export const handleIndicatorClick = ({ api, state }) => - (index) => { - api.canActive(index, state.activeIndex).then((result) => { - if (result) { - state.activeIndex = index - } - }) - } - -export const handleIndicatorHover = - ({ api, props, state }) => - (index) => { - if (props.trigger === 'hover' && index !== state.activeIndex) { + (index) => { api.canActive(index, state.activeIndex).then((result) => { if (result) { state.activeIndex = index } }) } - } + +export const handleIndicatorHover = + ({ api, props, state }) => + (index) => { + if (props.trigger === 'hover' && index !== state.activeIndex) { + api.canActive(index, state.activeIndex).then((result) => { + if (result) { + state.activeIndex = index + } + }) + } + } export const watchItems = ({ props, api }) => - (value) => { - if (value.length) { - api.setActiveItem(props.initialIndex) + (value) => { + if (value.length) { + api.setActiveItem(props.initialIndex) + } } - } export const watchActiveIndex = ({ emit, api }) => - ({ value, oldValue }) => { - api.resetItemPosition(oldValue) - emit('change', value, oldValue) - } + ({ value, oldValue }) => { + api.resetItemPosition(oldValue) + emit('change', value, oldValue) + } export const watchAutoplay = (api) => (value) => (value ? api.startTimer() : api.pauseTimer()) @@ -211,119 +211,119 @@ export const computedHasLabel = (items) => items.some((item) => item.label.toStr export const computedStyle = ({ props }) => - () => { - if (props.height) { - return { 'height': props.height } - } else { - // 低版本浏览器兼容(chrome 58 不支持 aspect-ratio属性) - if (CSS.supports('aspect-ratio', 'auto')) { - return { 'aspect-ratio': props.aspectRatio.replace(':', ' / ') } + () => { + if (props.height) { + return { 'height': props.height } } else { - const ratio = props.aspectRatio.split(':') - const paddingTop = ((ratio[1] / ratio[0]) * 100).toFixed(2) + '%' - - return { 'width': '100%', 'height': 0, 'padding-top': paddingTop } + // 低版本浏览器兼容(chrome 58 不支持 aspect-ratio 属性) + if (CSS.supports('aspect-ratio', 'auto')) { + return { 'aspect-ratio': props.aspectRatio.replace(':', ' / ') } + } else { + const ratio = props.aspectRatio.split(':') + const paddingTop = ((ratio[1] / ratio[0]) * 100).toFixed(2) + '%' + + return { 'width': '100%', 'height': 0, 'padding-top': paddingTop } + } } } - } export const onComplete = ({ api, count, emit, props, state }) => - (total) => { - if (count++ === total) { - state.completed = true + (total) => { + if (count++ === total) { + state.completed = true + + if (props.initialIndex < state.items.length && props.initialIndex >= 0) { + api.canActive(props.initialIndex, state.activeIndex).then((result) => { + if (result) { + state.activeIndex = props.initialIndex + } + }) + } - if (props.initialIndex < state.items.length && props.initialIndex >= 0) { - api.canActive(props.initialIndex, state.activeIndex).then((result) => { - if (result) { - state.activeIndex = props.initialIndex - } - }) + emit('complete') } - - emit('complete') } - } export const touchstart = ({ props, state, api }) => - (event) => { - if (state.items.length <= 1 || ~state.noTouchNode.indexOf(event.target.nodeName)) return + (event) => { + if (state.items.length <= 1 || ~state.noTouchNode.indexOf(event.target.nodeName)) return - if (!props.swipeable) { - event.preventDefault() - event.stopPropagation() - } + if (!props.swipeable) { + event.preventDefault() + event.stopPropagation() + } - resetTouchStatus(state) - api.pauseTimer() - state.itemsTranslate = state.items.map((item) => item.state.translate) + resetTouchStatus(state) + api.pauseTimer() + state.itemsTranslate = state.items.map((item) => item.state.translate) - state.moving = true - state.touchTime = Date.now() - state.startPos.X = event.touches[0].clientX - state.startPos.Y = event.touches[0].clientY - } + state.moving = true + state.touchTime = Date.now() + state.startPos.X = event.touches[0].clientX + state.startPos.Y = event.touches[0].clientY + } export const touchmove = ({ props, state, vm }) => - (event) => { - if (state.items.length <= 1 || ~state.noTouchNode.indexOf(event.target.nodeName)) return + (event) => { + if (state.items.length <= 1 || ~state.noTouchNode.indexOf(event.target.nodeName)) return - const touch = event.touches[0] - const itemsLen = state.items.length - const carousel = vm.$refs.carousel + const touch = event.touches[0] + const itemsLen = state.items.length + const carousel = vm.$refs.carousel - state.deltaPos.X = touch.clientX - state.startPos.X - state.deltaPos.Y = touch.clientY - state.startPos.Y - state.offsetPos.X = Math.abs(state.deltaPos.X) - state.offsetPos.Y = Math.abs(state.deltaPos.Y) - state.direction = state.direction || getDirection(state.offsetPos.X, state.offsetPos.Y) - state.isCorrectDirection = state.direction === props.type + state.deltaPos.X = touch.clientX - state.startPos.X + state.deltaPos.Y = touch.clientY - state.startPos.Y + state.offsetPos.X = Math.abs(state.deltaPos.X) + state.offsetPos.Y = Math.abs(state.deltaPos.Y) + state.direction = state.direction || getDirection(state.offsetPos.X, state.offsetPos.Y) + state.isCorrectDirection = state.direction === props.type - if (!state.isCorrectDirection) return + if (!state.isCorrectDirection) return - state.size = state.direction === 'horizontal' ? carousel.offsetWidth : carousel.offsetHeight + state.size = state.direction === 'horizontal' ? carousel.offsetWidth : carousel.offsetHeight - const nextIndex = state.activeIndex === itemsLen - 1 ? 0 : state.activeIndex + 1 - const prevIndex = state.activeIndex === 0 ? itemsLen - 1 : state.activeIndex - 1 + const nextIndex = state.activeIndex === itemsLen - 1 ? 0 : state.activeIndex + 1 + const prevIndex = state.activeIndex === 0 ? itemsLen - 1 : state.activeIndex - 1 - state.delta = state.direction === 'horizontal' ? state.deltaPos.X : state.deltaPos.Y + state.delta = state.direction === 'horizontal' ? state.deltaPos.X : state.deltaPos.Y - state.moveDisable = - !props.loop && - ((state.activeIndex === 0 && state.delta > 0) || - (state.activeIndex === state.items.length - 1 && state.delta < 0)) + state.moveDisable = + !props.loop && + ((state.activeIndex === 0 && state.delta > 0) || + (state.activeIndex === state.items.length - 1 && state.delta < 0)) - if (state.moveDisable) return + if (state.moveDisable) return - state.items[state.activeIndex].setDelta(state.delta) - state.items[nextIndex].setDelta(state.delta) + state.items[state.activeIndex].setDelta(state.delta) + state.items[nextIndex].setDelta(state.delta) - if (itemsLen > 2) { - state.items[prevIndex].setDelta(state.delta) + if (itemsLen > 2) { + state.items[prevIndex].setDelta(state.delta) + } } - } export const touchend = ({ state, api }) => - (event) => { - if (state.moveDisable || state.items.length <= 1 || ~state.noTouchNode.indexOf(event.target.nodeName)) return - const speed = state.delta / (Date.now() - state.touchTime) - const isShouldMove = Math.abs(speed) > 0.3 || Math.abs(state.delta) > +(state.size / 2).toFixed(2) - state.moving = false - state.itemsTranslate.forEach((item, index) => { - state.items[index].setDelta(0) - }) - if (isShouldMove && state.isCorrectDirection) { - state.delta < 0 ? api.next() : api.prev() - } else if (Math.abs(state.delta) > 1) { - state.items.forEach((item) => { - item.resetAnimatingMf() + (event) => { + if (state.moveDisable || state.items.length <= 1 || ~state.noTouchNode.indexOf(event.target.nodeName)) return + const speed = state.delta / (Date.now() - state.touchTime) + const isShouldMove = Math.abs(speed) > 0.3 || Math.abs(state.delta) > +(state.size / 2).toFixed(2) + state.moving = false + state.itemsTranslate.forEach((item, index) => { + state.items[index].setDelta(0) }) + if (isShouldMove && state.isCorrectDirection) { + state.delta < 0 ? api.next() : api.prev() + } else if (Math.abs(state.delta) > 1) { + state.items.forEach((item) => { + item.resetAnimatingMf() + }) + } + api.startTimer() } - api.startTimer() - } function resetTouchStatus(state) { state.direction = '' @@ -336,33 +336,134 @@ function resetTouchStatus(state) { export const simulateTouch = ({ props, vm }) => - () => { - if (props.swipeable && vm.$refs.carousel) { - emulate() - vm.$refs.carousel.setAttribute('data-tiny-touch-simulate-container', '') + () => { + if (props.swipeable && vm.$refs.carousel) { + emulate() + vm.$refs.carousel.setAttribute('data-tiny-touch-simulate-container', '') + } } - } export const computedHasButtons = ({ props, state, mode }) => - () => { - if (props.lite) { - return false - } else if (mode === 'mobile-first') { - return props.arrow !== 'never' && state.items.length > 1 - } else if (mode === 'pc') { - return props.arrow !== 'never' + () => { + if (props.lite) { + return false + } else if (mode === 'mobile-first') { + return props.arrow !== 'never' && state.items.length > 1 + } else if (mode === 'pc') { + return props.arrow !== 'never' + } } - } export const computedHasIndicators = ({ props, state, mode }) => - () => { - if (props.lite) { - return false - } else if (mode === 'mobile-first') { - return props.indicatorPosition !== 'none' && state.items.length > 1 - } else if (mode === 'pc') { - return props.indicatorPosition !== 'none' + () => { + if (props.lite) { + return false + } else if (mode === 'mobile-first') { + return props.indicatorPosition !== 'none' && state.items.length > 1 + } else if (mode === 'pc') { + return props.indicatorPosition !== 'none' + } } - } + + +//鼠标拖动切换走马灯 +export const mousedown = + ({ props, state, api }) => + (event) => { + if (state.items.length <= 1 || ~state.noTouchNode.indexOf(event.target.nodeName)) return + + if (!props.draggable) { + return + } + resetTouchStatus(state) + api.pauseTimer() + state.itemsTranslate = state.items.map((item) => item.state.translate) + + state.moving = true + state.touchTime = Date.now() + state.startPos.X = event.clientX + state.startPos.Y = event.clientY + } + +// 鼠标拖拽移动 +export const mousemove = + ({ props, state, vm }) => + throttle(100, true, (event) => { + if (!state.moving || state.items.length <= 1 || ~state.noTouchNode.indexOf(event.target.nodeName)) return + + const carousel = vm.$refs.carousel + if (!carousel) return + + //增加节流 - 我觉得可能引发一些问题,后续可以多看这个逻辑 + + state.deltaPos.X = event.clientX - state.startPos.X + state.deltaPos.Y = event.clientY - state.startPos.Y + state.offsetPos.X = Math.abs(state.deltaPos.X) + state.offsetPos.Y = Math.abs(state.deltaPos.Y) + state.direction = state.direction || getDirection(state.offsetPos.X, state.offsetPos.Y) + state.isCorrectDirection = state.direction === props.type + + if (!state.isCorrectDirection) return + + state.size = state.direction === 'horizontal' ? carousel.offsetWidth : carousel.offsetHeight + + const itemsLen = state.items.length + const nextIndex = state.activeIndex === itemsLen - 1 ? 0 : state.activeIndex + 1 + const prevIndex = state.activeIndex === 0 ? itemsLen - 1 : state.activeIndex - 1 + + state.delta = state.direction === 'horizontal' ? state.deltaPos.X : state.deltaPos.Y + + state.moveDisable = + !props.loop && + ((state.activeIndex === 0 && state.delta > 0) || + (state.activeIndex === state.items.length - 1 && state.delta < 0)) + + if (state.moveDisable) return + + state.items[state.activeIndex].setDelta(state.delta) + state.items[nextIndex].setDelta(state.delta) + + if (itemsLen > 2) { + state.items[prevIndex].setDelta(state.delta) + } + }) + +// 鼠标拖拽结束 +export const mouseup = + ({ state, api }) => + (event) => { + if (!state.moving || state.moveDisable || state.items.length <= 1 || ~state.noTouchNode.indexOf(event.target.nodeName)) return + + const speed = state.delta / (Date.now() - state.touchTime) + const isShouldMove = Math.abs(speed) > 0.3 || Math.abs(state.delta) > +(state.size / 2).toFixed(2) + + state.moving = false + state.itemsTranslate.forEach((item, index) => { + state.items[index].setDelta(0) + }) + + if (isShouldMove && state.isCorrectDirection) { + state.delta < 0 ? api.next() : api.prev() + } else if (Math.abs(state.delta) > 1) { + state.items.forEach((item) => { + item.resetAnimatingMf() + }) + } + + api.startTimer() + } + +// 鼠标离开窗口 +export const mouseleave = + ({ state, api }) => + (event) => { + if (state.moving) { + state.moving = false + state.itemsTranslate.forEach((item, index) => { + state.items[index].setDelta(0) + }) + api.startTimer() + } + } \ No newline at end of file diff --git a/packages/renderless/src/carousel/vue.ts b/packages/renderless/src/carousel/vue.ts index 7e174e562c..223fe0e018 100644 --- a/packages/renderless/src/carousel/vue.ts +++ b/packages/renderless/src/carousel/vue.ts @@ -27,7 +27,11 @@ import { simulateTouch, computedHasButtons, computedHasIndicators, - canActive + canActive, + mousedown, + mousemove, + mouseup, + mouseleave } from './index' import { addResizeListener, removeResizeListener } from '@opentiny/utils' @@ -58,7 +62,11 @@ export const api = [ 'throttledIndicatorHover', 'touchstart', 'touchmove', - 'touchend' + 'touchend', + 'mousedown', + 'mousemove', + 'mouseup', + 'mouseleave' ] const initState = ({ reactive, computed, api }) => { @@ -130,7 +138,12 @@ const initApi = ({ vm, api, state, props, emit, mode }) => { simulateTouch: simulateTouch({ props, vm }), computedHasButtons: computedHasButtons({ props, state, mode }), computedHasIndicators: computedHasIndicators({ props, state, mode }), - canActive: canActive(props) + canActive: canActive(props), + mousedown: mousedown({ props, state, api }), + mousemove: mousemove({ props, state, vm }), + mouseup: mouseup({ props,state, api }), + mouseleave: mouseleave({ props,state, api }), + //拖动相关的事件 }) } @@ -163,12 +176,14 @@ export const renderless = ( provide('CarouselVm', vm) initWatch({ watch, props, api, state }) + onMounted(() => { api.startTimer() api.onComplete(state.items.length) api.simulateTouch() }) + // 监听子组件 CarouselItem 提交的 complete 事件 parent.$on('updateItems', api.updateItems) diff --git a/packages/theme/src/carousel/index.less b/packages/theme/src/carousel/index.less index 07d5340e36..d6391c8f69 100644 --- a/packages/theme/src/carousel/index.less +++ b/packages/theme/src/carousel/index.less @@ -252,3 +252,10 @@ } } } + + +.tiny-carousel--draggable { + //鼠标效果 + cursor: grabbing; + user-select: none; +} \ No newline at end of file diff --git a/packages/vue/src/carousel/src/index.ts b/packages/vue/src/carousel/src/index.ts index 99e394532a..b8b2659d4b 100644 --- a/packages/vue/src/carousel/src/index.ts +++ b/packages/vue/src/carousel/src/index.ts @@ -75,7 +75,11 @@ export default defineComponent({ }, swipeable: Boolean, lite: Boolean, - beforeSwipe: Function + beforeSwipe: Function, + draggable: { + type: Boolean, + default: true + }, }, setup(props, context) { return $setup({ diff --git a/packages/vue/src/carousel/src/pc.vue b/packages/vue/src/carousel/src/pc.vue index cf0643750c..733f49efca 100644 --- a/packages/vue/src/carousel/src/pc.vue +++ b/packages/vue/src/carousel/src/pc.vue @@ -11,10 +11,15 @@ -->