viewer

    一个 MIP 页面除了在独立站点中展示,也会在结果页中被 SuperFrame 嵌入展现。
    因此,与 SuperFrame 的通信是必不可少的,这种通信通常是双向的。
    例如在 MIP 中每次发生页面切换,都需要通知 SuperFrame 进行浏览器地址栏 URL 的修改,当 SuperFrame 监听到历史记录的前进后退,也会通知 MIP router 进行跳转。

    下面将介绍向 SuperFrame 发送以及从 SuperFrame 接受消息的两个方法。

    • 参数:
      • {string} eventName 信息名称
      • {Object} data 信息内容
    • 返回值:无
    • 用法:

      用于向 外部 发送信息 (postMessage),仅在 MIP 页面被嵌入 SuperFrame 时生效。例如在百度搜索结果页中需要通知信息给外部的 SuperFrame 时可以使用。

    onMessage

    • 参数:
      • {string} eventName 信息名称
      • {Function} callback 收到特定信息后的回调函数。仅有一个参数:消息的具体信息。
    • 返回值:无
    • 用法:

      用于接收 外部 发送的信息 (postMessage),仅在 MIP 页面被嵌入 iframe 时生效。如在百度搜索结果页中需要接收外部的 SuperFrame 发来的信息时可以使用。

      1. MIP.viewer.onMessage('sfMessage', ({message}) => console.log(message))

    在 MIP 页面中点击 <a mip-link> 链接会在当前页面进行具有切换效果的页面跳转,类似 SPA 中的效果。
    除了这种方式,开发者也可以通过编程方式实现这一效果。

    • 参数:
      • {string} to 目标页面的 URL
      • {Object} options 配置对象
        • {boolean} options.isMipLink 默认 true。目标页面是否是 MIP 页。如果不是,直接使用 top.location.href 进行跳转。
        • {boolean} options.replace 默认 false。目标页面是否采用 replace 方式打开。如果是,则不会新增一条历史记录。
    • 返回值:无
    • 用法:

      以 API 的方式进行页面跳转,效果和 <a href="http://www.example.com" mip-link replace> 相同。

      1. // 替换当前历史记录,不新增
      2. MIP.viewer.open('http://www.example.com', {replace: true})

    MIP.viewer.page 作为 MIP.viewer 的重要组成部分,承担着页面切换,互相通信等等基本功能。
    我们暴露了一些常用的 API 供开发者使用。

    页面属性

    standalone: boolean

    MIP 页面的运行环境有两种:独立站点和百度搜索结果页。页面通过这个标志变量可以进行判断。

    • 用法:

      常量,不能更改。表示当前是否处于独立站点运行环境。

      • true: 当前页面处于独立站点运行环境
      • false: 当前页面处于百度搜索结果页运行环境
      1. if (MIP.viewer.page.standalone) {
      2. // 独立站点运行环境
      3. } else {
      4. // 百度搜索结果页中
      5. }

    isRootPage: boolean

    我们把打开的第一个页面称作“根页面”,后续打开新页面时,会在这个“根页面”中创建新的 iframe。
    由于运行环境不同,我们需要一个标志变量来区分“根页面”和后续页面。

    • 常量,不能更改。表示当前页面是否是“根页面”。

      • true: 当前页面是根页面
      • false: 当前页面不是根页面
      1. if (MIP.viewer.page.isRootPage) {
      2. // do something only in root page
      3. }

    isCrossOrigin: boolean

    我们支持打开一个跨域的 MIP 页面。
    如果页面想知道当前是否处于跨域状态,可以访问这个变量。

    • 用法:

      常量,不能更改。表示当前页面是否是跨域页面。

      • true: 当前页面是跨域页面
      1. if (MIP.viewer.page.isCrossOrigin) {
      2. // do something only in cross-origin page
      3. }

    对于历史记录的操作需要路由来完成。
    我们通过 page 暴露了两个快速操作路由的方法供开发者使用。

    back

    • 参数:无
    • 返回值:无
    • 用法:
      对历史记录进行后退操作

    forward

    • 参数:无
    • 返回值:无
    • 用法:
      对历史记录进行前进操作

      1. MIP.viewer.page.forward()

    滚动到锚点

    在一些场景下,滚动到某个锚点并且具有平滑的效果是很有用的。

    scrollToHash

    • 参数:锚点字符串
    • 返回值:无
    • 用法:
      滚动到某个锚点,如果在当前页面中不存在则无效果

      1. MIP.viewer.page.scrollToHash('#简介')

    页面切换事件

    在历史记录发生前进后退,即页面发生切换时,会在前后两个页面中触发切换事件,各个自定义组件可以监听这两个事件:

    • show-page 页面展示
    • hide-page 页面隐藏

    由于触发的是 CustomEvent 自定义事件,监听方法如下:

    1. // MyCustomComponent
    2. mounted() {
    3. window.addEventListener('show-page', () => {
    4. // do something when page show
    5. })
    6. window.addEventListener('hide-page', () => {
    7. // do something when page hide
    8. })
    9. }

    有一点需要注意,添加了监听处理之后,页面第一次展示时是不会触发 show-page 事件的。
    如果要针对首次展现编写处理逻辑,应该放在组件 mounted() 生命周期中执行。

    除了之前介绍过的和 SuperFrame 的通信,已经打开的多个 MIP 页面之间也需要进行通信。
    从发送的形式上看,可以分成向指定页面发送消息和向全部页面广播两种。
    而从发送消息的实际内容上看,开发者可以指定自定义事件 CustomEvent 的名称和附带数据,MIP 会确保在目标页面中触发这个自定义事件。作为接收者,只需要监听这个自定义事件即可。

    emitCustomEvent

    • 参数:
      • {Window} targetWindow 目标页面 window
      • {boolean} isCrossOrigin 是否跨域
      • {Object} event 事件对象
        • {string} event.name 自定义事件名称
        • {Object} event.data 自定义事件数据
    • 返回值:无
    • 用法:

      1. // 非跨域情况和 MIP Shell 进行通讯
      2. let target = page.isRootPage ? window : window.parent
      3. page.emitCustomEvent(target, false, {
      4. name: 'myShellEvent',
      5. data: {
      6. message: 'Hello MIP Shell'
      7. }
      8. })
      9. // MIP Shell 接收消息,可以实现在 MIP Shell 的继承子类中
      10. window.addEventListener('myShellEvent', e => {
      11. let data = e.detail[0]
      12. console.log(data.message) // Hello MIP Shell
      13. })

    broadcastCustomEvent

    • 参数:
      • {Object} event 事件对象
        • {string} event.name 自定义事件名称
        • {Object} event.data 自定义事件数据
    • 返回值:无
    • 用法:

      向所有页面广播自定义事件,此方法可跨域。常用于 Shell 向所有广播信息时使用(例如点击Shell底栏的夜间模式,通知所有窗口调整样式)。

      1. // MIP Shell 向所有页面广播信息,如点击打开“夜间模式”
      2. let page = window.MIP.viewer.page
      3. page.broadcastCustomEvent({
      4. name: 'myShellEvent',
      5. data: {
      6. nightMode: true
      7. }
      8. // 页面组件接受消息
      9. window.addEventListener('myShellEvent', e => {
      10. let data = e.detail[0]
      11. if (data.nightMode) {
      12. // 添加某些 class 到容器中,实现夜间模式
      13. }
      14. })

    isIframed: boolean

    • 用法:

      常量,不能更改。表示当前页面是否被嵌入到 iframe 中。

      • true: 页面处于 iframe 内
      • false: 页面不处于 iframe 内

    在 iOS 的 <iframe> 中,如果页面中包含了使用 display: fixed 定位的元素,在页面发生滚动时,这些元素并不会遵循 fixed 的特性停留在页面的同一位置,而会发生明显的上下跳动,十分影响视觉效果。

    为了解决这个问题,我们会使用一个相当 HACK 的做法:创建一个 <body> 的兄弟节点,将原有页面中所有的 fixed 元素移动到这个节点中,我们把这个节点叫做 fixedlayer 。此时原始的 HTML 结构会发生一个明显的改变,例如 <mip-fixed> 这样的元素被移动到了 fixedlayer 中,如下所示:

    1. <html>
    2. <body><!-- 原始内容 --></body>
    3. <body id="mip-fixedlayer">
    4. <!-- 所有 mip-fixed 元素被移动到了这里 -->
    5. <mip-fixed></mip-fixed>
    6. </body>
    7. </html>

    由于 HTML 结构的改变,在使用 <mip-fixed> 时有以下几点需要注意:

    1. 调用 MIP.viewer.fixedElement.init()
    2. 避免选择器嵌套
    3. 委托事件时选择

    下面我们将依次介绍这些注意事项。

    组件中使用

    在组件中使用 <mip-fixed> 时,一定要在 mounted() 生命周期中调用 fixedElement.init() 触发移动到 <body> 的操作。后续我们会将 <mip-fixed> 改造成真正的自定义组件,免去开发者手动调用的操作。

    fixedElement.init

    • 参数:无
    • 返回值:无
    • 用法:

      1. MIP.registerVueCustomElement('my-component', {
      2. template: `
      3. <div>
      4. <mip-fixed type="bottom">
      5. <div ="goToTop">Go to Top</div>
      6. </mip-fixed>
      7. </div>
      8. `,
      9. methods: {
      10. goToTop () {}
      11. },
      12. mounted () {
      13. MIP.viewer.fixedElement.init()
      14. }
      15. })

    由于 <mip-fixed> 位置发生了移动,原本定义在组件中的 CSS 选择器可能出现无法匹配的现象。
    例如选择器 .container > mip-fixed 无法匹配移动之后的 <mip-fixed> 元素,需要作出修改,取消嵌套规则。

    1. <template>
    2. <div class="container">
    3. <mip-fixed type="bottom"></mip-fixed>
    4. </div>
    5. </template>
    6. <style lang="stylus">
    7. // 不生效
    8. .container {
    9. mip-fixed {
    10. top: 100px
    11. }
    12. }
    13. // 生效
    14. mip-fixed {
    15. top: 100px
    16. }
    17. </style>

    委托事件

    1. <mip-fixed>
    2. <button class="my-button">My Button</button>
    3. </mip-fixed>
    1. // .my-button 已经不在 body 中了,因此并不会触发

    我们建议修改这类事件委托代码,可以参考 。