-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
624 additions
and
9 deletions.
There are no files selected for viewing
6 changes: 6 additions & 0 deletions
6
packages/taro-components/src/components/movable-area/area.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
taro-movable-area-core { | ||
display: block; | ||
height: 10px; | ||
width: 10px; | ||
position: relative; | ||
} |
169 changes: 164 additions & 5 deletions
169
packages/taro-components/src/components/movable-area/movable-area.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,176 @@ | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
import { Component, h, Host } from '@stencil/core' | ||
import { Component, h, Host, Prop, Element } from '@stencil/core' | ||
|
||
@Component({ | ||
tag: 'taro-movable-area-core' | ||
tag: 'taro-movable-area-core', | ||
styleUrl: './area.scss' | ||
}) | ||
export class MovableArea { | ||
|
||
/** | ||
* 当里面的 movable-view 设置为支持双指缩放时,设置此值可将缩放手势生效区域修改为整个movable-area | ||
*/ | ||
@Prop() scaleArea: boolean | ||
|
||
@Element() element: HTMLElement | ||
|
||
/** 观察者 */ | ||
private observer?: MutationObserver | ||
/** 子元素集合 */ | ||
private views: Array<HTMLElement> = [] | ||
/** 单个元素缩放时的目标元素 */ | ||
private scaleTarget?: HTMLElement | ||
/** 手势缩放 */ | ||
private scaleLength: number = 0 | ||
/** 宽高 */ | ||
private offset?: { width: number; height: number; } | ||
|
||
connectedCallback () { | ||
this.observer = new MutationObserver((mutations) => { | ||
mutations.forEach((mutation) => { | ||
if (mutation.attributeName === "class" || mutation.attributeName === "style") { | ||
const offsetWidth = this.element.offsetWidth | ||
const offsetHeight = this.element.offsetHeight | ||
if (offsetWidth !== this.offset?.width || offsetHeight !== this.offset?.height) { | ||
this.updateArea() | ||
} | ||
this.offset = { | ||
width: offsetWidth, | ||
height: offsetHeight | ||
} | ||
} | ||
}) | ||
}) | ||
|
||
this.observer.observe(this.element, { | ||
attributes: true | ||
}) | ||
} | ||
|
||
disconnectedCallback () { | ||
this.observer?.disconnect() | ||
} | ||
|
||
componentDidLoad () { | ||
console.error('H5 暂不支持 MovableArea 组件!') | ||
this.viewsChanged() | ||
} | ||
|
||
viewsChanged = () => { | ||
this.views = [] | ||
const elements = this.element.querySelectorAll('taro-movable-view-core') | ||
Array.from(elements).forEach((element) => { | ||
this.views.push(element) | ||
}) | ||
this.updateArea() | ||
} | ||
|
||
handleTouchStart = (e: TouchEvent) => { | ||
const touches = e.touches | ||
if (!touches || touches.length <= 1) { | ||
return | ||
} | ||
|
||
const gap = { | ||
width: touches[1].pageX - touches[0].pageX, | ||
height: touches[1].pageY - touches[0].pageY | ||
} | ||
this.scaleLength = Math.sqrt(gap.width * gap.width + gap.height * gap.height) | ||
if (this.scaleArea) { | ||
return | ||
} | ||
|
||
const find = (target: EventTarget, views: Array<HTMLElement>) => { | ||
const loop = (e, t) => { | ||
if (!(e = e.parentNode)) { | ||
return false | ||
} | ||
return (!(e instanceof HTMLElement) || e !== document.body) && (e === t || e === t.element || e.element === t || loop(e, t)) | ||
} | ||
|
||
for (let i = 0; i < views.length; i++) { | ||
const view = views[i] | ||
if (target === view["element"] || loop(target, view)) { | ||
return view | ||
} | ||
} | ||
} | ||
|
||
const touch1 = find(touches[0].target, this.views) | ||
const touch2 = find(touches[1].target, this.views) | ||
this.scaleTarget = touch1 && touch1 === touch2 ? touch1 : undefined | ||
} | ||
|
||
handleTouchMove = (e: TouchEvent) => { | ||
const touches = e.touches | ||
if (!touches || touches.length <= 1) { | ||
return | ||
} | ||
e.preventDefault() | ||
const gap = { | ||
width: touches[1].pageX - touches[0].pageX, | ||
height: touches[1].pageY - touches[0].pageY | ||
} | ||
if (this.scaleLength > 0) { | ||
this.updateScale(Math.sqrt(gap.width * gap.width + gap.height * gap.height) / this.scaleLength) | ||
} | ||
} | ||
|
||
handleTouchEnd = (e: TouchEvent) => { | ||
if ((e.touches && e.touches.length) || !e.changedTouches) { | ||
return | ||
} | ||
this.scaleLength = 0 | ||
if (this.scaleArea) { | ||
this.views.forEach((element) => { | ||
element["endScale"]?.() | ||
}) | ||
} else { | ||
this.scaleTarget?.["endScale"]?.() | ||
} | ||
this.scaleTarget = undefined | ||
} | ||
|
||
updateScale = (scale: number) => { | ||
if (!scale || scale === 1) { | ||
return | ||
} | ||
|
||
if (this.scaleArea) { | ||
this.views.forEach((element) => { | ||
element["setScale"]?.(scale) | ||
}) | ||
} else { | ||
this.scaleTarget?.["setScale"]?.(scale) | ||
} | ||
} | ||
|
||
updateArea = () => { | ||
const computedStyle = window.getComputedStyle(this.element) | ||
const clientRect = this.element.getBoundingClientRect() | ||
const horizontal = ["Left", "Right"].map((e) => { | ||
return parseFloat(computedStyle["border" + e + "Width"]) + parseFloat(computedStyle["padding" + e]) | ||
}) | ||
const vertical = ["Top", "Bottom"].map((e) => { | ||
return parseFloat(computedStyle["border" + e + "Width"]) + parseFloat(computedStyle["padding" + e]) | ||
}) | ||
|
||
this.views.forEach((element) => { | ||
element["setParent"]?.({ | ||
element: this.element, | ||
area: { | ||
height: clientRect.height - vertical[0] - vertical[1], | ||
width: clientRect.width - horizontal[0] - horizontal[1] | ||
} | ||
}) | ||
}) | ||
} | ||
|
||
render () { | ||
return ( | ||
<Host /> | ||
<Host | ||
onTouchStart={this.handleTouchStart} | ||
onTouchMove={this.handleTouchMove} | ||
onTouchEnd={this.handleTouchEnd} | ||
/> | ||
) | ||
} | ||
} |
Oops, something went wrong.