diff --git a/docs/api/ui/mediaQuery-observer.md b/docs/api/ui/mediaQuery-observer.md
new file mode 100644
index 0000000000000000000000000000000000000000..eb23a6eb68c0b642143c85acd1ddccc4ca0c31b2
--- /dev/null
+++ b/docs/api/ui/mediaQuery-observer.md
@@ -0,0 +1,134 @@
+节点布局交叉状态 API 可用于监听两个或多个组件节点在布局位置上的相交状态。这一组API常常可以用于推断某些节点是否可以被用户看见、有多大比例可以被用户看见。
+
+### uni.createIntersectionObserver([this], [options])
+创建并返回一个 ``IntersectionObserver`` 对象实例。
+
+**this说明:**
+
+自定义组件实例。**支付宝小程序不支持此参数,传入仅为抹平写法差异**
+
+**options 的可选参数为:**
+
+|字段名|类型|说明|
+|:-|:-|:-|
+|thresholds|Array|一个数值数组,包含所有阈值。默认为 ``[0]``。|
+|initialRatio|Number|初始的相交比例,如果调用时检测到的相交比例与这个值不相等且达到阈值,则会触发一次监听器的回调函数。默认为 ``0``。|
+|observeAll|Boolean|是否同时观测多个参照节点(而非一个),如果设为 ``true``,``observe`` 的 ``targetSelector`` 将选中多个节点(注意:同时选中过多节点将影响渲染性能)|
+
+### IntersectionObserver 对象的方法列表
+
+|方法|说明|
+|:-|:-|
+|IntersectionObserver.relativeTo(selector,[margins])|使用选择器指定一个节点,作为参照区域之一。|
+|IntersectionObserver.relativeToViewport([margins])|指定页面显示区域作为参照区域之一|
+|IntersectionObserver.observe(selector,[callback])|指定目标节点并开始监听相交状态变化情况。回调函数 ``callback`` 包含一个参数 ``result``|
+|IntersectionObserver.disconnect()|停止监听。回调函数将不再触发。|
+
+**margins 参数:** 用来扩展(或收缩)参照节点布局区域的边界。
+
+|属性|类型|默认值|是否必填|说明|
+|:-|:-|:-|:-|:-|
+|left|number||否|节点布局区域的左边界|
+|right|number||否|节点布局区域的右边界|
+|top|number||否|节点布局区域的上边界|
+|bottom|number||否|节点布局区域的下边界|
+
+下面的示例代码中,如果目标节点 ``".test"`` 进入 ``".scroll"`` 区域以下 100px 时,就会触发回调函数。
+```
+uni.createIntersectionObserver(this).relativeTo('.scroll',{bottom: 100}).observe('.test', (res) => {
+ console.log(res);
+})
+```
+
+**observe 回调函数 result 包含的字段**
+
+|字段名|类型|说明|
+|:-|:-|:-|
+|intersectionRatio|Number|相交比例|
+|intersectionRect|Object|相交区域的边界,包含 ``left``、``right``、``top``、``bottom`` 四项|
+|boundingClientRect|Object|目标节点布局区域的边界,包含 ``left``、``right``、``top``、``bottom`` 四项|
+|relativeRect|Object|参照区域的边界,包含 ``left``、``right``、``top``、``bottom`` 四项|
+|time|Number|相交检测时的时间戳|
+
+
+**Tips**
+
+- 与页面显示区域的相交区域并不准确代表用户可见的区域,因为参与计算的区域是“布局区域”,布局区域可能会在绘制时被其他节点裁剪隐藏(如祖先节点中 overflow 样式为 hidden 的节点)或遮盖(如 fixed 定位的节点)。
+- 节点交互状态 ``API`` 建议在 ``onReady`` 生命周期里监听,因为此 ``API`` 需要查找页面元素,``onReady`` 时页面已经完成初次渲染,已经能查找到对应的元素。
+
+### 代码示例
+
+```
+
+
+ {{appear ? '小球出现' : '小球消失'}}
+
+
+
+ 向下滚动让小球出现
+
+
+
+
+
+
+
+
+
+```
diff --git a/lib/apis.js b/lib/apis.js
index 2c22441e47e02e7b4a496733d841db9bfe74364f..853905a2fc037a68cd14adb4b35b213dc71b7be5 100644
--- a/lib/apis.js
+++ b/lib/apis.js
@@ -161,6 +161,7 @@ const ui = [
'stopPullDownRefresh',
'createSelectorQuery',
'createIntersectionObserver',
+ 'createMediaQueryObserver',
'getMenuButtonBoundingClientRect'
]
diff --git a/lib/modules.json b/lib/modules.json
index b9457b674dade7d3f135f85e2455d112856eb821..ce69a94a74f8e31f73390f5b78fa33001d7dec7c 100644
--- a/lib/modules.json
+++ b/lib/modules.json
@@ -113,15 +113,15 @@
"uni.getBLEDeviceServices": true,
"uni.getBLEDeviceCharacteristics": true,
"uni.createBLEConnection": true,
- "uni.closeBLEConnection": true,
- "uni.setBLEMTU": true,
+ "uni.closeBLEConnection": true,
+ "uni.setBLEMTU": true,
"uni.getBLEDeviceRSSI": true,
"uni.onBeaconServiceChange": true,
"uni.onBeaconUpdate": true,
"uni.getBeacons": true,
"uni.startBeaconDiscovery": true,
- "uni.stopBeaconDiscovery": true,
- "uni.onThemeChange": true,
+ "uni.stopBeaconDiscovery": true,
+ "uni.onThemeChange": true,
"uni.onUIStyleChange": true
}
}, {
@@ -157,7 +157,8 @@
"uni.stopPullDownRefresh": true,
"uni.createSelectorQuery": true,
"uni.createIntersectionObserver": true,
- "uni.hideKeyboard": true,
+ "uni.createMediaQueryObserver": true,
+ "uni.hideKeyboard": true,
"uni.onKeyboardHeightChange": true
}
}, {
diff --git a/src/core/service/api/ui/create-media-query-observer.js b/src/core/service/api/ui/create-media-query-observer.js
new file mode 100644
index 0000000000000000000000000000000000000000..0139d84dee6c4df897cc03500a669a51c2743091
--- /dev/null
+++ b/src/core/service/api/ui/create-media-query-observer.js
@@ -0,0 +1,47 @@
+import createCallbacks from 'uni-helpers/callbacks'
+
+import {
+ getCurrentPageVm
+} from '../../platform'
+
+const createMediaQueryObserverCallbacks = createCallbacks('requestMediaQueryObserver')
+
+class ServiceMediaQueryObserver {
+ constructor (component, options) {
+ this.pageId = component.$page.id
+ this.component = component._$id || component // app-plus 平台传输_$id
+ this.options = options
+ }
+
+ observe (options, callback) {
+ if (typeof callback !== 'function') {
+ return
+ }
+ this.options = options
+
+ this.reqId = createMediaQueryObserverCallbacks.push(callback)
+
+ UniServiceJSBridge.publishHandler('requestMediaQueryObserver', {
+ reqId: this.reqId,
+ component: this.component,
+ options: this.options
+ }, this.pageId)
+ }
+
+ disconnect () {
+ UniServiceJSBridge.publishHandler('destroyMediaQueryObserver', {
+ reqId: this.reqId
+ }, this.pageId)
+ }
+}
+
+export function createMediaQueryObserver (context, options) {
+ if (!context._isVue) {
+ options = context
+ context = null
+ }
+ if (context) {
+ return new ServiceMediaQueryObserver(context, options)
+ }
+ return new ServiceMediaQueryObserver(getCurrentPageVm('createMediaQueryObserver'), options)
+}
diff --git a/src/core/service/bridge/subscribe.js b/src/core/service/bridge/subscribe.js
index c5e6dee2dd0a9d68f2381b2d1e442f1b83b0d108..27f11c9f1f8dc8bb5083b5ddafd5d057c18baf68 100644
--- a/src/core/service/bridge/subscribe.js
+++ b/src/core/service/bridge/subscribe.js
@@ -50,6 +50,23 @@ export default function initSubscribe (subscribe, {
}
}
+ const requestMediaQueryObserverCallbacks = createCallbacks('requestMediaQueryObserver')
+
+ function onRequestMediaQueryObserver ({
+ reqId,
+ reqEnd,
+ res
+ }) {
+ const callback = requestMediaQueryObserverCallbacks.get(reqId)
+ if (callback) {
+ if (reqEnd) {
+ requestMediaQueryObserverCallbacks.pop(reqId)
+ return
+ }
+ callback(res)
+ }
+ }
+
if (__PLATFORM__ === 'h5') {
subscribe('onPageReady', createPageEvent('onReady'))
}
@@ -59,4 +76,5 @@ export default function initSubscribe (subscribe, {
subscribe('onRequestComponentInfo', onRequestComponentInfo)
subscribe('onRequestComponentObserver', onRequestComponentObserver)
+ subscribe('onRequestMediaQueryObserver', onRequestMediaQueryObserver)
}
diff --git a/src/core/service/plugins/index.js b/src/core/service/plugins/index.js
index 34617aee545039ecb52f5ed3ecb77cf025f74c3f..01aa6e1d88615fcd65b7754b130eb9d7b7761347 100644
--- a/src/core/service/plugins/index.js
+++ b/src/core/service/plugins/index.js
@@ -73,7 +73,7 @@ export default {
initPolyfill(Vue)
lifecycleMixin(Vue)
-
+
/* eslint-disable no-undef */
if (typeof __UNI_ROUTER_BASE__ !== 'undefined') {
__uniConfig.router.base = __UNI_ROUTER_BASE__
@@ -194,7 +194,11 @@ export default {
return uni.createIntersectionObserver(this, args)
}
+ Vue.prototype.createMediaQueryObserver = function createMediaQueryObserver (args) {
+ return uni.createMediaQueryObserver(this, args)
+ }
+
Vue.use(VueRouter)
}
-}
+}
diff --git a/src/core/service/plugins/polyfill.js b/src/core/service/plugins/polyfill.js
index c5ebb7461a0b53df27756eff902777b03a9afbe8..7e9c53cb560e22b0e2f1a58e61e8103b7bd683ac 100644
--- a/src/core/service/plugins/polyfill.js
+++ b/src/core/service/plugins/polyfill.js
@@ -70,6 +70,10 @@ export function initPolyfill (Vue) {
return uni.createIntersectionObserver(this, options)
}
+ Vue.prototype.createMediaQueryObserver = function createMediaQueryObserver (options) {
+ return uni.createMediaQueryObserver(this, options)
+ }
+
Vue.prototype.selectComponent = function selectComponent (selector) {
return querySelector(this, parseSelector(selector))
}
@@ -77,4 +81,4 @@ export function initPolyfill (Vue) {
Vue.prototype.selectAllComponents = function selectAllComponents (selector) {
return querySelectorAll(this, parseSelector(selector), [])
}
-}
+}
diff --git a/src/core/view/bridge/subscribe/api/index.js b/src/core/view/bridge/subscribe/api/index.js
index 24a0ca32cc597fac6bf31b4fe3ed135446e2d9a7..615e752f5936970ebd58c300c78148963a869e8a 100644
--- a/src/core/view/bridge/subscribe/api/index.js
+++ b/src/core/view/bridge/subscribe/api/index.js
@@ -11,9 +11,16 @@ import {
destroyComponentObserver
} from './request-component-observer'
+import {
+ requestMediaQueryObserver,
+ destroyMediaQueryObserver
+} from './request-media-query-observer'
+
export default {
setPageMeta,
requestComponentInfo,
requestComponentObserver,
- destroyComponentObserver
-}
+ destroyComponentObserver,
+ requestMediaQueryObserver,
+ destroyMediaQueryObserver
+}
diff --git a/src/core/view/bridge/subscribe/api/request-media-query-observer.js b/src/core/view/bridge/subscribe/api/request-media-query-observer.js
new file mode 100644
index 0000000000000000000000000000000000000000..1b0b553179fde78d4a943ee54497b33cebc22761
--- /dev/null
+++ b/src/core/view/bridge/subscribe/api/request-media-query-observer.js
@@ -0,0 +1,76 @@
+const mediaQueryObservers = {}
+const listeners = {} // 用公用对象存储监听器
+
+// 拼接媒体查询条件
+function handleMediaQueryStr ($props) {
+ let mediaQueryStr = []
+ const propsMenu = [
+ 'width',
+ 'minWidth',
+ 'maxWidth',
+ 'height',
+ 'minHeight',
+ 'maxHeight',
+ 'orientation'
+ ]
+ for (const item of propsMenu) {
+ if (item !== 'orientation' && $props[item] !== '' && Number($props[item]) >= 0) {
+ mediaQueryStr.push(`(${humpToLine(item)}: ${Number($props[item])}px)`)
+ }
+ if (item === 'orientation' && $props[item]) {
+ mediaQueryStr.push(`(${humpToLine(item)}: ${$props[item]})`)
+ }
+ }
+ mediaQueryStr = mediaQueryStr.join(' and ')
+ return mediaQueryStr
+}
+
+function humpToLine (name) {
+ return name.replace(/([A-Z])/g, '-$1').toLowerCase()
+}
+
+// 请求媒体查询对象
+export function requestMediaQueryObserver ({
+ reqId,
+ options
+}, pageId) {
+ const pages = getCurrentPages()
+ const page = pages.find(page => page.$page.id === pageId)
+
+ if (!page) {
+ throw new Error(`Not Found:Page[${pageId}]`)
+ }
+
+ const pageVm = page.$vm
+
+ // 创建一个媒体查询对象
+ const mediaQueryObserver = mediaQueryObservers[reqId] = window.matchMedia(handleMediaQueryStr(options))
+
+ // 创建一个监听器
+ const listener = listeners[reqId] = e => {
+ UniViewJSBridge.publishHandler('onRequestMediaQueryObserver', {
+ reqId,
+ res: e.matches
+ }, pageVm.$page.id)
+ }
+
+ listener(mediaQueryObserver) // 监听前执行一次媒体查询
+ mediaQueryObserver.addListener(listener)
+}
+
+// 销毁媒体查询对象
+export function destroyMediaQueryObserver ({
+ reqId
+}) {
+ const listener = listeners[reqId] // 需要移除的某个监听
+ const mediaQueryObserver = mediaQueryObservers[reqId]
+
+ if (mediaQueryObserver) {
+ mediaQueryObserver.removeListener(listener) // 移除监听
+ delete mediaQueryObservers[reqId]
+ UniViewJSBridge.publishHandler('onRequestMediaQueryObserver', {
+ reqId,
+ reqEnd: true
+ })
+ }
+}