Skip to content

Commit

Permalink
feat: movable
Browse files Browse the repository at this point in the history
  • Loading branch information
baosiqing authored and baosiqing committed Aug 3, 2022
1 parent f7541d4 commit cc169f7
Show file tree
Hide file tree
Showing 4 changed files with 624 additions and 9 deletions.
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 packages/taro-components/src/components/movable-area/movable-area.tsx
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}
/>
)
}
}
Loading

0 comments on commit cc169f7

Please sign in to comment.