属性和事件
属性和事件
本节介绍所有原生组件都提供的通用属性接口以及事件。
属性列表
通用属性
top
组件顶部相对于父级原生组件的位置,单位为像素。此属性实际上是内联样式中 top 属性的简写,更多的使用方法详见组件位置操作。
读取或监听 top 属性时会得到组件已计算的位置,也及时布局后的实际测量值。
left
组件左侧相对于父级原生组件的位置,单位为像素。此属性实际上是内联样式中 left 属性的简写,更多的使用方法详见组件位置操作。
读取或监听 left 属性时会得到组件已计算的位置,也及时布局后的实际测量值。
width
组件的宽度。在有布局的容器中设置此属性将会被忽略。
读取或监听 width 属性时会得到组件已计算的位置,也及时布局后的实际测量值。
height
设置组件的高度。在有布局的容器中设置此属性将会被忽略。
读取或监听 height 属性时会得到组件已计算的位置,也及时布局后的实际测量值。
show
设置组件是否可见。隐藏的组件不会显示,也不占据布局空间。
quiescent
设置组件快照是否自动更新(静止快照)。如果组件通过快照显示,此属性的值为 false 时(默认值)组件内容更新时会立即刷新快照以更新视图,否则不会立即更新快照。将此属性设置为 true 可以提高 UI 性能,但会造成显示内容滞后。
下面的示例展示了 quiescent 属性的作用。界面中有两个 p 元素被放置在 scroll 容器内,且 scroll 容器开启了快照模式,当用户滚动 scroll 组件时会对其中的元素截取快照。由于第一个 p 元素是普通快照模式而第二个 p 元素则为静止快照模式,因此滚动时只能观察到第一个 p 元素的内容更新。
<scroll snapshot scroll-snap="center">
<p>normal snapshot {{ count }}</p>
<p quiescent>quiescent snapshot {{ count }}</p>
</scroll>
scroll {
display: flex;
flex-direction: column;
background-color: lightgray;
}
p {
background-color: lightgreen;
text-align: center;
padding: 10px;
margin: 10px;
}
export default {
data: {
count: 0
},
onReady(event) {
setInterval(() => this.count++, 500)
}
}
style
设置组件的内联样式。目前只支持带有 内联 标签的 CSS 属性。
z-index
z-index 属性设置元素的 Z 轴顺序,z-index 较大的重叠元素会覆盖较小的元素。该属性值会被 CSS 中的 z-index 属性覆盖。
opacity
指定组件的透明度,值范围是 ,其中 表示完全透明。和 CSS 属性 opacity 效果相同。
注意
opacity 值会影响元素的绘制性能,详情请参考 opacity CSS 属性的说明。
transform
设置组件的变换,等效于 CSS 的 transform 属性。
disabled
用于设置或获取组件的禁用状态。当属性值为 true 时,元素处于禁用状态,用户无法与其交互,元素将不响应任何手势(如点击、拖动等)。当属性值为默认的 false 时,组件处于可用状态,用户可以正常与其交互。
下面的示例演示了 disabled 属性的用法,同时还用 :disabled CSS 伪类控制样式。该示例展示了 div 元素在普通状态下可以响应点击手势,但是在 disabled 状态下不响应任何手势。
<div :disabled="disabled" on:click="onClick">
{{disabled ? 'disabled' : 'normal'}} <switch />
</div>
div {
background-color: lightgray;
text-align: center;
display: flex;
justify-content: center;
}
/* :disabled 伪类可以控制元素在 disabled 状态下的样式 */
div:disabled {
opacity: 0.5;
}
import prompt from '@system.prompt'
export default {
data: {
disabled: false
},
onInit() {
setInterval(() => {
this.disabled = !this.disabled
}, 2000)
},
onClick() {
prompt.showToast({ message: 'clicked!', duration: 250 })
}
}
通用事件
大部分原生组件都支持通用事件,它们可以用 on 指令进行监听。这些事件的值类型在事件类型节介绍。
touchstart
用户开始触摸组件时触发 touchstart 事件。事件值是 TouchEvent 类型。
touchmove
用户触点在组件上移动时触发 touchmove 事件,在移动过程中即使触点离开了当前原生组件的范围也会一直触发此事件。事件值是 TouchEvent 类型。
触摸状态从 touchstart 转换到 touchmove 存在一定的“移动死区”,如果用户触摸的滑动距离小于死区范围则不会触发 touchmove。移动死区范围因设备而异,下面的例子展示了移动死区。
<p on:touchstart="state = 'start'"
on:touchmove="onTouchMove($event)"
on:touchend="onTouchEnd">
{{ `state: ${state} \ndead area: (${dx}, ${dy})` }}
</p>
p {
background-color: lightgreen;
text-align: center;
}
export default {
data: {
state: null,
dx: null,
dy: null
},
onTouchMove(event) {
if (!this.dx && !this.dy) {
this.state = 'move'
this.dx = event.touches[0].offsetX
this.dy = event.touches[0].offsetY
}
},
onTouchEnd() {
this.state = 'end'
this.dx = this.dy = null
}
}
touchend
用户触点离开屏幕时会对之前触摸的原生组件发送 touchend 事件。事件值是 TouchEvent 类型。
touchcancel
当原生组件的触摸被中断时触发 touchcancel。事件值是 TouchEvent 类型。有多种原因可能导致触摸中断,例如组件被隐藏或者触摸事件被其他元素强制响应等。
click
当原生组件被点击并松手时触发 click 事件。事件值是 ClickEvent 类型。
<p on:click="click = JSON.stringify($event)">
{{ click }}
</p>
p {
background-color: lightgreen;
text-align: center;
}
export default {
data: {
click: null
}
}
longpress
当原生组件被长时间按压时触发 longpress 事件。事件值是 LongPressEvent 类型。下面的可交互示例展示了 longpress 和其他事件的触发时机:
<p on:touchstart="state = 'touching...'"
on:longpress="state = `longpress: ${JSON.stringify($event)}`"
on:click="state = 'clicked.'">
{{ state }}
</p>
p {
background-color: lightgreen;
text-align: center;
}
export default {
data: {
state: null
}
}
longpress 事件的触发时机和持续时间因设备而异,通常在按压 后触发。不同于 click 事件,longpress 在按压期间触发,而不是松手时触发。对于上面的示例,你会发现:
- 当按压时间小于长按触发时间时,松手后会触发
click事件; - 按压的足够久时会触发
longpress事件,松手后触发click事件(显示为“clicked.”状态); - 按压过程中发生移动将不会触发
longpress或click事件。
swipe
当组件被快速扫动时触发 swipe 事件。事件值是 SwipeEvent 类型。
<p on:swipe="onSwipe($event)">
{{ swipe }}
</p>
p {
background-color: lightgreen;
text-align: center;
}
export default {
data: {
swipe: null
},
onSwipe(event) {
this.swipe = event.direction
event.strongResponse()
}
}
keydown
当按键按下时触发此事件。keydown 和 keyup 事件用于捕获实体按键的操作。要想捕获事件,原生组件必须处于焦点状态,页面的根元素总是会自动获取焦点,因此下面的代码可以捕获到 keydown 和 keyup 事件:
<!-- 假设这是页面的根元素 -->
<div on:keydown="console.log($event)" on:keyup="console.log($event)">
...
</div>
事件值类型请参考 KeyEvent。
手表设备通常会注册默认按键处理程序,因此应用代码即使不响应这类事件也可以进行交互(例如按下 Power 键时一些手表会返回上一页)。要想阻止默认按键响应,可使用 KeyEvent 对象的 stopPropagation() 方法来阻止冒泡。
keyup
当按键抬起时触发此事件。更多内容请参考 keydown 事件。
wheel
当用户操作旋转滚轮时触发 wheel 事件。滚轮设备包括手表的旋转表冠,或者鼠标滚轮等。想要捕获此时间,原生组件必须处于焦点状态,页面的根元素总是会自动获取焦点,因此下面的代码可以捕获到 wheel 事件:
<!-- 假设这是页面的根元素 -->
<div on:wheel="console.log($event)">
...
</div>
事件值类型请参考 WheelEvent。
事件类型
BaseEvent
BaseEvent 事件对象提供一些控制事件传递的方法,其原型是:
interface BaseEvent {
strongResponse(): void, // 强制响应事件
stopPropagation(): void // 停止事件冒泡
}
TouchEvent
TouchEvent 事件对象的原型为:
interface TouchEvent extends BaseEvent {
isTarget: boolean, // 事件目标是否为当前组件
touches: { // 本事件所有的触摸点数据
clientX: number, // 触摸点相对于目标组件内容区域的 x 坐标
clientY: number, // 触摸点相对于目标组件内容区域的 y 坐标
offsetX: number, // 触摸点在触摸过程中 x 方向的位移量
offsetY: number // 触摸点在触摸过程中 y 方向的位移量
}[];
}
ClickEvent
SwipeEvent 事件对象的原型是:
interface SwiperEvent extends BaseEvent {
isTarget: boolean, // 事件目标是否为当前组件
clientX: number, // 点击触摸点相对于目标组件内容区域的 x 坐标
clientY: number // 点击触摸点相对于目标组件内容区域的 y 坐标
}
LongPressEvent
LongPressEvent 事件对象的原型是:
interface SwiperEvent extends BaseEvent {
isTarget: boolean, // 事件目标是否为当前组件
clientX: number, // 长按触摸点相对于目标组件内容区域的 x 坐标
clientY: number // 长按触摸点相对于目标组件内容区域的 y 坐标
}
SwipeEvent
SwipeEvent 事件对象的原型是:
interface SwiperEvent extends BaseEvent {
isTarget: boolean, // 事件目标是否为当前组件
direction: 'left' | 'right' | 'up' | 'down' // 扫动方向
}
KeyEvent
KeyEvent 对象描述了用户对实体按键的交互事件,该类型用于元素 keydown 和 keyup 的事件属性。KeyEvent 事件对象的原型是:
interface KeyEvent {
type: 'keydown' | 'keyup', // 按键事件的类型
key: string, // 按键名称
timestamp: number, // 按键事件上报的时间戳,单位是毫秒
stopPropagation(): void // 调用此方法可以阻止事件冒泡
}
目前支持以下按键名称:
'Power':手表的电源键;'Fn':手表的功能键;- 其他可打印字符的按键以单个字符构成键名,例如字母
'A'、减号'-'等。
WheelEvent
WheelEvent 对象描述了用户对旋转滚轮的交互事件,该类型用于元素 wheel 的事件属性。WheelEvent 事件对象的签名是:
interface WheelEvent {
deltaY: number, // 滚轮在 y 方向的滚动增量
stopPropagation(): void // 调用此方法可以阻止事件冒泡
}
与 Web 的 wheel event 不同,Glyphix 中的 WheelEvent 目前只包含 deltaY 属性。
事件响应机制
事件冒泡
触摸和手势事件支持冒泡(bubbling)。冒泡是指当事件发生在一个元素上,它会首先执行该元素上的处理程序,然后执行其父元素上的处理程序,然后一直向上到其他祖先上的处理程序。下面的例子中,绿色的 p 组件和灰色的 div 组件都监听了触摸事件,其中在点击 p 组件时会观察到 p 组件和 div 组件都能接收到事件。
<div on:touchstart="onTouch('div', $event)"
on:touchmove="onTouch('div', $event)"
on:touchend="onRelease('div', $event)">
<p on:touchstart="onTouch('p', $event)"
on:touchmove="onTouch('p', $event)"
on:touchend="onRelease('p', $event)">
{{ `touchs: ${touchs.div ? 'div' : '-'} ${touchs.p ? 'p' : '-'}, target: ${target}` }}
</p>
</div>
div {
display: flex;
flex-direction: column;
background-color: lightgray;
justify-content: space-around;
}
p {
background-color: lightgreen;
text-align: center;
height: 150px;
}
export default {
data: {
touchs: { div: false, p: false },
target: null
},
onTouch(name, event) {
this.touchs[name] = true
// isTarget 属性可以区分事件的目标是否是当前监听该事件的组件
if (event.isTarget)
this.target = name
},
onRelease(name, event) {
this.touchs[name] = false
if (event.isTarget)
this.target = null
}
}
在 Glyphix 中,只有本文档中的触摸和手势事件会冒泡。目前不能在 JavaScript 代码中进行事件捕获。
阻止事件冒泡
使用 BaseEvent 的 stopPropagation() 方法可以阻止事件向父级冒泡。
强响应事件
在 Glyphix 中触摸或手势事件有两种响应优先级:强响应和弱响应。当一个事件同时有多个待响应的目标时强响应的优先级高于弱响应。假设界面上存在 3 级父子元素:A -> B -> C,其中 C 对事件是弱响应的,而 B 是强响应,那么事件将派发给 B 之后就不会再派发到 C 了。一个原本强响应事件的元素在改为弱响应之后会重新派发事件。
通用事件中的触摸和手势事件默认是弱响应的。在下面的例子中,一个绿色的 p 组件被放置在灰色的 scroll 内,并且监听了 p 组件的所有触摸事件。由于 scroll 默认强响应上下滑动的手势,弱响应左右滑动手势,且不响应其他手势,所以在操作中可以观察到:
- 点击
p组件时会触发touchstart事件,松手时触发touchend事件; - 横向拖拽
p组件时会触发touchmove事件; - 上下拖拽
p组件时,由于父级scroll组件对上下滑动有强响应,而模板代码中p组件对touchmove只有弱响应,所以上下滑动会被scroll组件响应,p组件会收到touchcancel事件。
<scroll>
<p on:touchstart="state = 'touchstart'"
on:touchmove="state = 'touchmove'"
on:touchend="state = 'touchend'"
on:touchcancel="state = 'touchcancel'">
{{ `p.state: ${state}` }}
</p>
</scroll>
scroll {
background-color: lightgray;
}
p {
background-color: lightgreen;
text-align: center;
height: 150px;
margin: 50px;
}
export default {
data: {
state: null
}
}
很多原生组件的默认手势事件处理机制是强响应的。使用 BaseEvent 对象的 strongResponse() 方法可以在 JavaScript 代码中指定事件为强响应模式。下面的例子中外层灰色的 div 组件会强响应手势,因此即使触摸内部的 p 元素,在手势开始之后事件会只派发给 div 元素。
<div on:touchstart="onTouch('div', 'start', $event)"
on:touchmove="onTouch('div', 'move', $event)"
on:touchend="onTouch('div', 'end', $event)"
on:touchcancel="onTouch('div', 'cancel', $event)">
<p on:touchstart="onTouch('p', 'start', $event)"
on:touchmove="onTouch('p', 'move', $event)"
on:touchend="onTouch('p', 'end', $event)"
on:touchcancel="onTouch('p', 'cancel', $event)">
{{ `div state: ${touchs.div}, p state: ${touchs.p}, target: ${target}` }}
</p>
</div>
div {
display: flex;
flex-direction: column;
background-color: lightgray;
justify-content: space-around;
}
p {
background-color: lightgreen;
text-align: center;
height: 150px;
}
export default {
data: {
touchs: { div: null, p: null },
target: null
},
onTouch(name, state, event) {
console.log(name, state, event.isTarget)
this.touchs[name] = state
// isTarget 属性可以区分事件的目标是否是当前监听该事件的组件,
// 如果是 cancel 事件就不记录目标。
if (event.isTarget && state != 'cancel')
this.target = name
if (name == 'div')
event.strongResponse()
}
}
页面的默认事件处理
页面默认会弱响应手势事件并且阻止事件冒泡,因此手势事件无法透过页面进行派发和传递。另外页面会在收到向右的 touchmove 手势时退出,开发者也可以拦截手势以禁用此特性。
具体的做法是监听页面组件的 touchmove 手势并阻止冒泡:
<!-- 这个 div 是页面的根组件 -->
<div on:touchmove="$event.stopPropagation()">
...
</div>
这样,这个页面就无法通过右滑操作返回,但是可以通过按下实体 Power 键返回。要先阻止用户按键返回,可以使用以下方式:
<!-- 这个 div 是页面的根组件 -->
<div on:keydown="onKeyup">
...
</div>
export default {
onKeyup(event) {
// 判定键值为 'Power' 时禁止事件冒泡以阻止页面退出
if (event.key == 'Power')
event.stopPropagation()
}
}
注意
谨慎替代页面的默认事件处理机制,避免出现用户无法返回页面的情况。
提示
之前的版本中,通过 swipe 手势事件来阻止页面的默认返回行为,但是在 0.6.4 版本中已经废弃了这种方式。请使用上述的 touchmove 事件处理来替代。这一调整是由于页面的交互式返回动效(即跟手退出)完全无法兼容 swipe 阻止页面返回的语义导致的。
使用技巧
组件位置操作
利用原生组件的 top 和 left 属性可以轻松地修改组件位置:
<div :top="40" :left="20"> ... </div>
top 和 left 实际上是同名 CSS 属性的简写,因此它们只会在绝对布局中生效,可以通过以下 CSS 来实现:
div {
position: absolute;
}
然后你可以使用响应式的属性来修改组件的位置。下面的例子展示了结合 transition 修饰符所实现的带动画的随机组件位置移动。
<div id="pane">
<p id="tile" :top="top" :left="left"
top.transition left.transition>
Tile
</p>
</div>
div {
background-color: lightgray;
}
p {
/* 要使用组件的 top / left 属性就必须是绝对定位 */
position: absolute;
background-color: lightgreen;
text-align: center;
width: 3rem;
height: 3rem;
border: 4px solid red;
border-radius: 10%;
}
export default {
data: {
top: 0,
left: 0
},
timer: null,
onReady() {
// 获取组件对象,位置范围不应超出 #pane 容器
const pane = this.$element("pane")
const tile = this.$element("tile")
const width = pane.width - tile.width
const height = pane.height - tile.height
this.timer = setInterval(() => {
this.top = Math.random() * height
this.left = Math.random() * width
}, 2000)
},
onDestroy() {
clearInterval(this.timer)
}
}
本示例每隔两秒钟随机设置一次 #tile 组件的位置,且范围不超出容器 #pane 的边界。默认的 transition 修饰符会播放 秒钟的过渡动画。
