diff --git a/components/mip-orientation-observer/README.md b/components/mip-orientation-observer/README.md
new file mode 100644
index 00000000..afe50359
--- /dev/null
+++ b/components/mip-orientation-observer/README.md
@@ -0,0 +1,196 @@
+# mip-orientation-observer 组件
+
+用于监听设备移动的方向信息,发出 `alpha` `beta` `gamma` 事件,提供给其他 MIP 组件使用。
+
+标题|内容
+----|----
+类型|通用
+支持布局|nodisplay
+所需脚本| https://c.mipcdn.com/static/v2/mip-orientation-observer/mip-orientation-observer.js
+
+## 示例
+
+### 基本使用
+
+组件本身只会发出事件,不会做其他任何事情,所以需要结合 `mip-animation` 等组件使用。
+
+截止到 2019 年 6 月,组件的兼容性如下:
+
+
+
+可见,Safari 浏览器暂时不提供支持。同时,不同版本的手机操作系统和浏览器,以及不同的应用程序中内置的浏览器对 deviceorientation 事件的支持不尽相同。尤其在Android平台上,可能会出现有的设备正常工作,有的则毫无反应的情况。
+
+所以,mip-orientation-observer 仅推荐结合 `mip-animation` 或 `mip-video` 等媒体组件使用。
+
+## 属性
+
+### alpha-range
+
+说明:指定仅对设备绕 z 轴旋转指定范围角度内触发事件。当设备逆时针旋转时,alpha 值增加,当设备顺时针旋转时,alpha 值由 360 开始减少。
+
+>注意:alpha 增加是逆时针旋转
+
+
+
+必选项:若监听 `alpha` 事件必写,监听其他事件不必须
+
+类型:2 个数值以空格连接,比如 alpha-range =“0 180”
+
+默认值:0 到 360 度
+
+### beta-range
+
+说明:指定仅对设备绕 x 轴旋转指定范围角度内触发事件。当设备的顶部和底部与地球表面等距时 beta 值为 0°。当设备的顶部远离地球表面时,此值增加,当设备的顶部倾向地球表面时,此值减少,为负数。
+
+
+
+必选项:若监听 `beta` 事件必写,监听其他事件不必须
+
+类型:2 个数值以空格连接,比如 alpha-range =“0 180”
+
+默认值:-180 到 180 度
+
+### gamma-range
+
+说明:指定仅对设备绕 y 轴旋转指定范围角度内触发事件。当设备的左右边缘与地球表面等距时 gamma 值为 0°。 当设备的右侧倾向地球表面时,此值增加,当设备的左侧倾向地球表面时,此值减少,为负数。
+
+
+
+必选项:若监听 `gamma` 事件必写,监听其他事件不必须
+
+类型:2 个数值以空格连接,比如 alpha-range =“0 180”
+
+默认值:-90 到 90 度
+
+### duration
+
+说明:指明动画持续时间,与 `mip-animation` 的 `duration` 值相同
+
+必选项:使用 `mip-animation` 的 `seekTo` 功能时,必须写明动画持续时间,用于组件计算并返回 `seekToTime`
+
+类型:数字
+
+默认值:无,不写则 `seekTo` 不生效
+
+## 注意
+
+### 关于角度范围
+
+组件顺时针取弧长,作为触发范围。比如:
+
+若 `alpha-range = "330 30"`,则取 330 度到 360 度和 0 度到 30 度的弧长作为事件触发范围,360 度与 0 度重合
+
+若 `alpha-range = "90 30"`,则取 90 度到 360 度和 0 度到 30 度的弧长作为事件触发范围,即 30 度顺时针到 90 度的弧不触发事件
+
+若 `beta-range = "-30 30"`,则取 -30 度到 0 度和 0 度到 30 度的弧长作为事件触发范围
+
+若 `beta-range = "120 -120"`,则取 120 度到 180 度和 -180 度到 -120 度的弧长作为事件触发范围,180 度与 -180 度重合
+
+若 `gamma-range = "-30 30"`,则取 -30 度到 0 度和 0 度到 30 度的弧长作为事件触发范围
+
+`gamma-range` 仅支持 -90 度到 0 度和 0 度到 90 度的顺时针弧长监听
+
+### 事件参数
+
+`alpha` `beta` `gamma` 事件传出数据形如:
+
+```js
+orientData = {
+ 'angle': 30,
+ 'percent': 0.2333,
+ 'seekToTime': 120
+}
+```
+
+可以在 `on` 事件中获取:
+
+```html
+
+
+```
+
+### 关于为什么角度设置感觉和正常想象的不一样
+
+组件的时间和角度设置与原生 `deviceorientation` 事件保持一致,组件可以实现为更符合人操作的方向和角度。但是为了使开发者使用原生事件时不发生混淆,所以与原生事件保持一致。
+
+## 示例
+
+```html
+
alpha-range="330 30",监听范围为手机顺时针和逆时针旋转各 30 度。手机放平,起始状态指向为 0 度,逆时针向左转 30 度到达 330,顺时针向右转 30 到达 30。动画初始状态为动画执行到一半时的状态,逆时针转动手机,alpha 值增大,方块向右移动;顺时针转动手机,alpha 值减小,方块向左。
+
+
+
+
+
+
+
+
+
+
+ beta-range="-30 30",监听范围为手机顶部向下旋转和向上旋转各 30 度。手机放平,起始状态指向为 0 度,手机顶部向上 30 度到达 30,向下 30 到达 -30。动画初始状态为动画执行到一半时的状态,手机顶部向上转动,beta 值增大,方块向右移动;手机顶部向下转动,beta 值减小,方块向左。
+
+
+
+
+
+
+
+
+
+
+ gamma-range="-30 30",监听范围为手机右侧向下旋转和向上旋转各 30 度。手机放平,起始状态指向为 0 度,手机右侧向下 30 度到达 30,向上 30 到达 -30。动画初始状态为动画执行到一半时的状态,手机右侧向下转动,gamma 值增大,方块向右移动;手机右侧向上转动,gamma 值减小,方块向左。
+
+
+
+
+
+
+
+```
diff --git a/components/mip-orientation-observer/example/mip-orientation-observer.html b/components/mip-orientation-observer/example/mip-orientation-observer.html
new file mode 100644
index 00000000..b0f14b95
--- /dev/null
+++ b/components/mip-orientation-observer/example/mip-orientation-observer.html
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+ mip-orientation-observer
+
+
+
+
+
+
+ 以下所有描述中,0 度都是手机放平时的起始状态
+
+
+ alpha-range="330 30"
+
+ 监听范围:
+ 手机顺时针和逆时针旋转各 30 度,超过范围不触发事件,即动画无反应。
+
+
+ 操作:
+ 逆时针向左转 30 度到达 330,顺时针向右转 30 到达 30。手机初始状态为 0,是度数 [-30, 30] 的中间值,所以动画初始是执行到一半时的状态,逆时针转动手机,alpha 值增大,方块向右移动;顺时针转动手机,alpha 值减小,方块向左。
+
+
+
+
+
+
+
+
+
+
+ beta-range="-30 30"
+
+ 监听范围:
+ 手机顶部向下旋转和向上旋转各 30 度
+
+
+ 操作:
+ 手机顶部向上 30 度到达 30,向下 30 到达 -30。手机初始状态为 0,所以动画出于执行到一半时的状态,手机顶部向上转动,beta 值增大,方块向右移动;手机顶部向下转动,beta 值减小,方块向左。
+
+
+
+
+
+
+
+
+
+
+ gamma-range="-30 30"
+
+ 监听范围:
+ 手机右侧向下旋转和向上旋转各 30 度
+
+
+ 操作:
+ 手机右侧向下 30 度到达 30,向上 30 度到达 -30。动画初始状态为执行到一半时的状态,手机右侧向下转动,gamma 值增大,方块向右移动;手机右侧向上转动,gamma 值减小,方块向左。
+
+
+
+
+
+
+
+
+
+
+ alpha-range="10 270"
+
+ 监听范围:
+ 手机逆时针旋转 10 度到 270 度
+
+
+ 操作:
+ 逆时针向左转 10 度到达 10,一直旋转直到到达 270。手机初始状态为 0,不在触发范围内,动画未触发,元素处于屏幕最左边。逆时针转动手机时,触发动画。alpha 值增大,方块向右移动;顺时针转动手机,alpha 值减小,方块向左。
+
+
+
+
+
+
+
+
+
+
+ beta-range="30 -120"
+
+ 监听范围:
+ 手机顶部向上旋转 30 度到达 30,保持旋转可到达 -120
+
+
+ 操作:
+ 手机初始状态为 0,不在触发范围内,元素处于屏幕最左边。手机顶部向上转动,保持转动,beta 值增大,方块向右移动;手机顶部向下转动,beta 值减小,方块向左。
+
+
+ 解释:
+ 手机顶部向上旋转 30 度到达 30,一直保持方向再旋转到达 180 度(也是 -180 度,两点重合),接着旋转成负值 -180,由 -180 渐渐变大直到到达 -120。
+
+
+
+
+
+
+
+
+
+
+ gamma-range="-60 30"
+
+ 监听范围:
+ 手机右侧向下旋转 30 度到和向上旋转 60度
+
+
+ 操作:
+ 手机顶部向上转动,保持转动,beta 值增大,方块向右移动;手机顶部向下转动,beta 值减小,方块向左。
+
+
+ 解释:
+ 手机右侧从 0 状态向下 30 度到达 30,从 0 状态向上 60 到达 -60。手机右侧向下转动,gamma 值增大,方块向右移动;手机右侧向上转动,gamma 值减小,方块向左。
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/mip-orientation-observer/mip-orientation-observer.js b/components/mip-orientation-observer/mip-orientation-observer.js
new file mode 100644
index 00000000..ab1d22d9
--- /dev/null
+++ b/components/mip-orientation-observer/mip-orientation-observer.js
@@ -0,0 +1,105 @@
+
+const {CustomElement, util, viewer} = MIP
+const {warn} = util.log('mip-orientation-observer')
+const {raf} = util.fn
+
+export default class MIPOrientationOberver extends CustomElement {
+ constructor (element) {
+ super(element)
+ this.alphaRange = undefined
+ this.betaRange = undefined
+ this.gammaRange = undefined
+ }
+
+ firstInviewCallback () {
+ if (!window.DeviceOrientationEvent) {
+ warn('当前浏览器不支持 window.DeviceOrientationEvent')
+ return
+ }
+
+ this.alphaRange = this.parseAttributes('alpha-range', this.alphaRange)
+ this.betaRange = this.parseAttributes('beta-range', this.betaRange)
+ this.gammaRange = this.parseAttributes('gamma-range', this.gammaRange)
+ this.duration = this.element.getAttribute('duration') || 0
+ window.addEventListener('deviceorientation', event => {
+ raf(() => {
+ this.deviceOrientationHandler(event)
+ })
+ }, true)
+ }
+
+ parseAttributes (rangeName, originalRange) {
+ const providedRange = this.element.getAttribute(rangeName)
+ if (providedRange) {
+ const rangeArray = providedRange.trim().split(' ')
+ return [parseInt(rangeArray[0], 10), parseInt(rangeArray[1], 10)]
+ }
+ return originalRange
+ }
+
+ deviceOrientationHandler (event) {
+ if (event instanceof DeviceOrientationEvent) {
+ const canTirggerAlpha = this.canTirgger('alpha', event.alpha)
+ if (canTirggerAlpha) {
+ this.triggerEvent('alpha', event.alpha, this.alphaRange)
+ }
+ const canTirggerBeta = this.canTirgger('beta', event.beta)
+ if (canTirggerBeta) {
+ this.triggerEvent('beta', event.beta, this.betaRange)
+ }
+ const canTirggerGamma = this.canTirgger('gamma', event.gamma)
+ if (canTirggerGamma) {
+ this.triggerEvent('gamma', event.gamma, this.gammaRange)
+ }
+ }
+ }
+
+ canTirgger (name, value) {
+ let range = this[`${name}Range`]
+ return range && (range[0] < range[1]
+ ? value > range[0] && value < range[1]
+ : value > range[0] || value < range[1])
+ }
+
+ triggerEvent (eventName, eventValue, eventRange) {
+ // alpha 旋转角度取值范围为 [0, 360],beta 取值范围为 [-180, 180],gamma 旋转角度取值范围为 [-90, 90]
+ // 处理触发弧正常情况:alpha 不跨越 0 度和 360 重合点,beta 不跨越 -180 度和 180 度重合点
+ // gamma 本身仅支持顺时针 [-90, 90] 的情况,不存在 0 或者 -180 度这样的跳跃点,不用特殊处理
+ // 度数定义见 README 或者 html deviceorientation官方文档
+ let arcLen = eventRange[1] - eventRange[0]
+ let percentValue = eventValue - eventRange[0]
+ let percent = percentValue / arcLen
+ let orientData = {
+ 'angle': Math.round(eventValue),
+ 'percent': percent,
+ 'seekToTime': Math.floor(percent * this.duration)
+ }
+
+ // 处理触发弧包含跳跃点的情况,比如
+ // alpha-range = "330 30" 或者 beta-range = "120 -120"
+ if (eventRange[0] > eventRange[1]) {
+ arcLen = eventRange[1] - eventRange[0] + 360
+ if (eventName === 'alpha') {
+ percentValue = eventValue > eventRange[1]
+ // 当前角度在 330 度顺时针到 0 度时
+ ? eventValue - eventRange[0]
+ // 当前角度在 0 度顺时针到 30 度时
+ : eventValue + (360 - eventRange[0])
+ }
+ if (eventName === 'beta') {
+ percentValue = eventValue < eventRange[1]
+ // 当前角度在 -180 度顺时针到 -120 度这样的范围时
+ ? eventValue + (360 - eventRange[0])
+ // 当前角度在 120 度顺时针到 -180 度时
+ : eventValue - eventRange[0]
+ }
+ percent = percentValue / arcLen
+ orientData = {
+ 'angle': Math.round(eventValue),
+ 'percent': percent,
+ 'seekToTime': Math.floor(percent * this.duration)
+ }
+ }
+ viewer.eventAction.execute(eventName, this.element, orientData)
+ }
+}