From 490223d5392459ed4c439f9428aa0d59eae056b5 Mon Sep 17 00:00:00 2001 From: zy19940510 Date: Mon, 8 Feb 2021 18:51:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9D=E5=A7=8B=E5=8C=96range?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.js | 10 ++ src/packages/range/demo.vue | 33 +++++ src/packages/range/doc.md | 34 +++++ src/packages/range/index.scss | 63 +++++++++ src/packages/range/index.vue | 255 ++++++++++++++++++++++++++++++++++ src/sites/mobile/main.ts | 2 + src/utils/touchEmulator.js | 197 ++++++++++++++++++++++++++ src/utils/useRect/index.ts | 48 +++++++ src/utils/useTouch/index.ts | 69 +++++++++ 9 files changed, 711 insertions(+) create mode 100644 src/packages/range/demo.vue create mode 100644 src/packages/range/doc.md create mode 100644 src/packages/range/index.scss create mode 100644 src/packages/range/index.vue create mode 100644 src/utils/touchEmulator.js create mode 100644 src/utils/useRect/index.ts create mode 100644 src/utils/useTouch/index.ts diff --git a/src/config.js b/src/config.js index 00253d6f1..e937bdfe1 100644 --- a/src/config.js +++ b/src/config.js @@ -313,6 +313,16 @@ module.exports = { sort: 15, show: true, author: 'yangxiaolu' + }, + { + version: '3.0.0', + name: 'Range', + type: 'component', + cName: '区间选择器', + desc: '滑动输入条,用于在给定的范围内选择一个值。', + sort: 16, + show: true, + author: 'Jerry' } ] }, diff --git a/src/packages/range/demo.vue b/src/packages/range/demo.vue new file mode 100644 index 000000000..33d529218 --- /dev/null +++ b/src/packages/range/demo.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/src/packages/range/doc.md b/src/packages/range/doc.md new file mode 100644 index 000000000..cd0702498 --- /dev/null +++ b/src/packages/range/doc.md @@ -0,0 +1,34 @@ +# range组件 + + ### 介绍 + + 基于 xxxxxxx + + ### 安装 + + + + ## 代码演示 + + ### 基础用法1 + + + + ## API + + ### Props + + | 参数 | 说明 | 类型 | 默认值 | + |--------------|----------------------------------|--------|------------------| + | name | 图标名称或图片链接 | String | - | + | color | 图标颜色 | String | - | + | size | 图标大小,如 '20px' '2em' '2rem' | String | - | + | class-prefix | 类名前缀,用于使用自定义图标 | String | 'nutui-iconfont' | + | tag | HTML 标签 | String | 'i' | + + ### Events + + | 事件名 | 说明 | 回调参数 | + |--------|----------------|--------------| + | click | 点击图标时触发 | event: Event | + \ No newline at end of file diff --git a/src/packages/range/index.scss b/src/packages/range/index.scss new file mode 100644 index 000000000..388b6f537 --- /dev/null +++ b/src/packages/range/index.scss @@ -0,0 +1,63 @@ +.nut-range { + display: block; + position: relative; + width: 100%; + height: 3px; + background-color: rgba(255, 163, 154, 1); + border-radius: 2px; + cursor: pointer; + &::before { + position: absolute; + top: -8px; + right: 0; + bottom: -8px; + left: 0; + content: ''; + } + + &-bar { + display: block; + position: relative; + width: 100%; + height: 100%; + background: linear-gradient( + 135deg, + rgba(250, 44, 25, 1) 0%, + rgba(250, 63, 25, 1) 45%, + rgba(250, 89, 25, 1) 83%, + rgba(250, 100, 25, 1) 100% + ); + border-radius: inherit; + transition: all 0.2s; + } + + &-button { + display: block; + width: 24px; + height: 24px; + background-color: #fff; + border-radius: 50%; + box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.15); + border: 1px solid rgba(250, 44, 25, 1); + outline: none; + + &-wrapper, + &-wrapper-right { + position: absolute; + top: 50%; + right: 0; + transform: translate3d(50%, -50%, 0); + cursor: grab; + outline: none; + } + + &-wrapper-left { + position: absolute; + top: 50%; + left: 0; + transform: translate3d(-50%, -50%, 0); + cursor: grab; + outline: none; + } + } +} diff --git a/src/packages/range/index.vue b/src/packages/range/index.vue new file mode 100644 index 000000000..ea41d9a1f --- /dev/null +++ b/src/packages/range/index.vue @@ -0,0 +1,255 @@ + + + + diff --git a/src/sites/mobile/main.ts b/src/sites/mobile/main.ts index 6f1b68902..bcc95f687 100644 --- a/src/sites/mobile/main.ts +++ b/src/sites/mobile/main.ts @@ -3,6 +3,8 @@ import App from './App.vue'; import router from './router'; import NutUI from '@/nutui'; import '@/sites/assets/styles/reset.scss'; +import '@/utils/touchEmulator'; + createApp(App) .use(router) .use(NutUI) diff --git a/src/utils/touchEmulator.js b/src/utils/touchEmulator.js new file mode 100644 index 000000000..cfa71b1c0 --- /dev/null +++ b/src/utils/touchEmulator.js @@ -0,0 +1,197 @@ +/* eslint-disable */ +/** + * Emulate touch event + * Source:https://github.com/hammerjs/touchemulator + */ + +var eventTarget; +var supportTouch = 'ontouchstart' in window; + +// polyfills +if (!document.createTouch) { + document.createTouch = function( + view, + target, + identifier, + pageX, + pageY, + screenX, + screenY + ) { + // auto set + return new Touch( + target, + identifier, + { + pageX: pageX, + pageY: pageY, + screenX: screenX, + screenY: screenY, + clientX: pageX - window.pageXOffset, + clientY: pageY - window.pageYOffset + }, + 0, + 0 + ); + }; +} + +if (!document.createTouchList) { + document.createTouchList = function() { + var touchList = TouchList(); + for (var i = 0; i < arguments.length; i++) { + touchList[i] = arguments[i]; + } + touchList.length = arguments.length; + return touchList; + }; +} + +/** + * create an touch point + * @constructor + * @param target + * @param identifier + * @param pos + * @param deltaX + * @param deltaY + * @returns {Object} touchPoint + */ + +var Touch = function Touch(target, identifier, pos, deltaX, deltaY) { + deltaX = deltaX || 0; + deltaY = deltaY || 0; + + this.identifier = identifier; + this.target = target; + this.clientX = pos.clientX + deltaX; + this.clientY = pos.clientY + deltaY; + this.screenX = pos.screenX + deltaX; + this.screenY = pos.screenY + deltaY; + this.pageX = pos.pageX + deltaX; + this.pageY = pos.pageY + deltaY; +}; + +/** + * create empty touchlist with the methods + * @constructor + * @returns touchList + */ +function TouchList() { + var touchList = []; + + touchList['item'] = function(index) { + return this[index] || null; + }; + + // specified by Mozilla + touchList['identifiedTouch'] = function(id) { + return this[id + 1] || null; + }; + + return touchList; +} + +/** + * only trigger touches when the left mousebutton has been pressed + * @param touchType + * @returns {Function} + */ + +var initiated = false; +function onMouse(touchType) { + return function(ev) { + // prevent mouse events + + if (ev.type === 'mousedown') { + initiated = true; + } + + if (ev.type === 'mouseup') { + initiated = false; + } + + if (ev.type === 'mousemove' && !initiated) { + return; + } + + // The EventTarget on which the touch point started when it was first placed on the surface, + // even if the touch point has since moved outside the interactive area of that element. + // also, when the target doesnt exist anymore, we update it + if ( + ev.type === 'mousedown' || + !eventTarget || + (eventTarget && !eventTarget.dispatchEvent) + ) { + eventTarget = ev.target; + } + + triggerTouch(touchType, ev); + + // reset + if (ev.type === 'mouseup') { + eventTarget = null; + } + }; +} + +/** + * trigger a touch event + * @param eventName + * @param mouseEv + */ +function triggerTouch(eventName, mouseEv) { + var touchEvent = document.createEvent('Event'); + touchEvent.initEvent(eventName, true, true); + + touchEvent.altKey = mouseEv.altKey; + touchEvent.ctrlKey = mouseEv.ctrlKey; + touchEvent.metaKey = mouseEv.metaKey; + touchEvent.shiftKey = mouseEv.shiftKey; + + touchEvent.touches = getActiveTouches(mouseEv); + touchEvent.targetTouches = getActiveTouches(mouseEv); + touchEvent.changedTouches = createTouchList(mouseEv); + + eventTarget.dispatchEvent(touchEvent); +} + +/** + * create a touchList based on the mouse event + * @param mouseEv + * @returns {TouchList} + */ +function createTouchList(mouseEv) { + var touchList = TouchList(); + touchList.push(new Touch(eventTarget, 1, mouseEv, 0, 0)); + return touchList; +} + +/** + * receive all active touches + * @param mouseEv + * @returns {TouchList} + */ +function getActiveTouches(mouseEv) { + // empty list + if (mouseEv.type === 'mouseup') { + return TouchList(); + } + return createTouchList(mouseEv); +} + +/** + * TouchEmulator initializer + */ +function TouchEmulator() { + window.addEventListener('mousedown', onMouse('touchstart'), true); + window.addEventListener('mousemove', onMouse('touchmove'), true); + window.addEventListener('mouseup', onMouse('touchend'), true); +} + +// start distance when entering the multitouch mode +TouchEmulator['multiTouchOffset'] = 75; + +if (!supportTouch) { + new TouchEmulator(); +} diff --git a/src/utils/useRect/index.ts b/src/utils/useRect/index.ts new file mode 100644 index 000000000..215fa6069 --- /dev/null +++ b/src/utils/useRect/index.ts @@ -0,0 +1,48 @@ +/** + 获取元素的大小及其相对于视口的位置,等价于 Element.getBoundingClientRect。 + width 宽度 number + height 高度 number + top 顶部与视图窗口左上角的距离 number + left 左侧与视图窗口左上角的距离 number + right 右侧与视图窗口左上角的距离 number + bottom 底部与视图窗口左上角的距离 number + */ + +import { Ref, unref } from 'vue'; + +function isWindow(val: unknown): val is Window { + return val === window; +} + +export const useRect = ( + elementRef: (Element | Window) | Ref +) => { + const element = unref(elementRef); + + if (isWindow(element)) { + const width = element.innerWidth; + const height = element.innerHeight; + + return { + top: 0, + left: 0, + right: width, + bottom: height, + width, + height + }; + } + + if (element && element.getBoundingClientRect) { + return element.getBoundingClientRect(); + } + + return { + top: 0, + left: 0, + right: 0, + bottom: 0, + width: 0, + height: 0 + }; +}; diff --git a/src/utils/useTouch/index.ts b/src/utils/useTouch/index.ts new file mode 100644 index 000000000..7f7929ffb --- /dev/null +++ b/src/utils/useTouch/index.ts @@ -0,0 +1,69 @@ +import { ref } from 'vue'; + +const MIN_DISTANCE = 10; + +type Direction = '' | 'vertical' | 'horizontal'; + +function getDirection(x: number, y: number) { + if (x > y && x > MIN_DISTANCE) { + return 'horizontal'; + } + if (y > x && y > MIN_DISTANCE) { + return 'vertical'; + } + return ''; +} + +export function useTouch() { + const startX = ref(0); + const startY = ref(0); + const deltaX = ref(0); + const deltaY = ref(0); + const offsetX = ref(0); + const offsetY = ref(0); + const direction = ref(''); + + const isVertical = () => direction.value === 'vertical'; + const isHorizontal = () => direction.value === 'horizontal'; + + const reset = () => { + deltaX.value = 0; + deltaY.value = 0; + offsetX.value = 0; + offsetY.value = 0; + direction.value = ''; + }; + + const start = ((event: TouchEvent) => { + reset(); + startX.value = event.touches[0].clientX; + startY.value = event.touches[0].clientY; + }) as EventListener; + + const move = ((event: TouchEvent) => { + const touch = event.touches[0]; + deltaX.value = touch.clientX - startX.value; + deltaY.value = touch.clientY - startY.value; + offsetX.value = Math.abs(deltaX.value); + offsetY.value = Math.abs(deltaY.value); + + if (!direction.value) { + direction.value = getDirection(offsetX.value, offsetY.value); + } + }) as EventListener; + + return { + move, + start, + reset, + startX, + startY, + deltaX, + deltaY, + offsetX, + offsetY, + direction, + isVertical, + isHorizontal + }; +} -- GitLab