# page-container

组件类型:UniPageContainerElement

弹层容器组件,效果类似于 popup 弹出层,当该组件处于显示状态时,用户进行返回操作,会关闭组件而不关闭当前页面

page-container 的特点:

  • 与普通前端popup类组件相比,page-container组件能响应返回操作。返回操作具体指:右滑手势、安卓物理返回键、和调用 navigateBack 接口三种返回情形。小程序未提供监听返回的API,想实现返回操作关闭弹层而不是页面,只能使用page-container组件。
  • dialogPage相比,page-container 是组件而不是页面;page-container 跨端,而 dialogPage 不支持小程序;dialogPage 支持覆盖pages.json中定义的顶部导航栏和tabbar,而 page-container不支持。

# 兼容性

Web 微信小程序 Android iOS HarmonyOS HarmonyOS(Vapor)
5.02 4.41 5.02 5.02 5.02 5.02

# 属性

名称 类型 默认值 兼容性 描述
show boolean false
是否显示容器组件
duration number 300
动画时长,单位毫秒
z-index number 100
z-index 层级
overlay boolean true
是否显示遮罩层
round boolean false
是否显示圆角
position top | left | bottom | right | center "bottom"
弹出位置
合法值 兼容性 描述
top
顶部
left
左侧
bottom
底部
right
右侧
center
居中
close-on-slide-down boolean false
是否在下滑一段距离后关闭
overlay-style string(string.CSSString) -
自定义遮罩层样式
custom-style string(string.CSSString) -
自定义弹出层样式
@beforeenter eventhandle -
(eventhandle)
进入前触发
@enter eventhandle -
(eventhandle)
进入中触发
@afterenter eventhandle -
(eventhandle)
进入后触发
@beforeleave eventhandle -
(eventhandle)
离开前触发
@leave eventhandle -
(eventhandle)
离开中触发
@afterleave eventhandle -
(eventhandle)
离开后触发
@clickoverlay (event: UniPointerEvent) => void -
点击遮罩层时触发

# Tips

  • uni ui组件库中曾广泛使用的uni-popup组件,在uni-app x中推荐改用 page-container 组件替代
  • 组件支持拦截用户的返回操作,包括右滑手势、安卓物理返回键和调用 navigateBack API
  • Web 设置 overlay: true 时,组件会禁止背景页面滚动,避免滚动穿透
  • 小程序 uni.navigateBack 无法在页面栈顶调用,此时没有上一级页面
  • 小程序不支持 左侧弹出,App 和 Web 支持
  • 微信小程序 enterleave 相关事件的回调函数有参数 event,App 和 Web 平台没有
  • 开启 closeOnSlideDown 后,微信小程序需要快速下滑才生效,App 和 Web 会跟着手指拖动滑动
  • 小程序页面最多只有1个page-container,若已存在page-container的情况下,无法新弹出page-container。App 和 Web 支持弹出多个page-container组件,后弹覆盖先弹。
  • Web 暂不支持拦截侧滑返回和浏览器的后退按钮
  • 微信小程序 positioncenterright 时,容器是全屏的,App 和 Web 平台不是

# 示例

示例为hello uni-app x alpha分支,与最新HBuilderX Alpha版同步。与最新正式版同步的master分支示例另见

该 API 不支持 Web,请运行 hello uni-app x 到 App 平台体验

<template>
  <scroll-view style="flex: 1">
    <page-intro content="本页演示 page-container 页面容器,用于在页面内创建弹出层效果,支持拦截返回操作:顶部/底部/左侧/右侧/居中弹出、圆角、遮罩与透明蒙层、下滑关闭等能力,通过按钮触发不同展示。"></page-intro>
    <view class="uni-padding-wrap uni-common-mt">
      <view class="uni-title uni-common-mt">
        <text class="uni-title-text"> 弹出位置 </text>
      </view>
      <view>
        <button @click="showContainer('top', '顶部')">顶部弹出</button>
        <button class="mt-5" @click="showContainer('bottom', '底部')">底部弹出</button>
        <button id="right-button" class="mt-5" @click="showContainer('right', '右侧')">右侧弹出</button>
        <!-- #ifndef MP -->
        <button class="mt-5" @click="showContainer('left', '左侧')">左侧弹出</button>
        <!-- #endif -->
        <button class="mt-5" @click="showContainer('center', '居中')">居中弹出</button>
      </view>
    </view>

    <view class="uni-padding-wrap uni-common-mt">
      <view class="uni-title uni-common-mt">
        <text class="uni-title-text"> 弹窗圆角 </text>
      </view>
      <view>
        <button @click="showRound">圆角</button>
      </view>
    </view>

    <view class="uni-padding-wrap uni-common-mt">
      <view class="uni-title uni-common-mt">
        <text class="uni-title-text"> 遮罩层 </text>
      </view>
      <view>
        <button @click="showOverlay(false)">不显示蒙层</button>
        <button class="mt-5" @click="showOverlay(true)">显示蒙层</button>
        <button class="mt-5" @click="showTransparentOverlay">透明蒙层</button>
        <button class="mt-5" @click="showGreenTransparentOverlay">绿色半透明蒙层</button>
      </view>
    </view>

    <view class="uni-padding-wrap uni-common-mt">
      <view class="uni-title uni-common-mt">
        <text class="uni-title-text"> 自动隐藏 </text>
      </view>
      <view>
        <button @click="autoClose">2秒后自动关闭</button>
      </view>
    </view>

    <view class="uni-padding-wrap uni-common-mt">
      <view class="uni-title uni-common-mt">
        <text class="uni-title-text"> 滚动穿透 </text>
      </view>
      <view>
        <button @click="showScrollThrough">打开滚动穿透测试弹层</button>
        <text class="slider-down-info">测试弹层内滚动到顶部或底部时是否会影响主页面滚动</text>
      </view>
    </view>

    <view class="uni-padding-wrap uni-common-mt">
      <view class="uni-title uni-common-mt">
        <text class="uni-title-text"> 下滑关闭 </text>
      </view>
      <view>
        <button @click="showSlideDown">支持下滑关闭</button>
        <text class="slider-down-info">提示: 当 close-on-slide-down=true 时,可下滑关闭容器</text>
      </view>
    </view>

    <view class="uni-padding-wrap uni-common-mt" style="margin-bottom: 30px;">
      <view class="uni-title uni-common-mt">
        <text class="uni-title-text"> 多层堆叠测试 </text>
      </view>
      <view>
        <button @click="showStackedLayer1">打开第1层弹层</button>
      </view>
    </view>

    <page-container :show="containerShow" :position="containerPosition" :round="containerRound"
      :overlay="containerOverlay" :overlay-style="containerOverlayStyle"
      :close-on-slide-down="containerCloseOnSlideDown" @afterleave="onAfterLeave">
        <view class="container">
          <text class="container-title">{{ containerTitle }}</text>
          <scroll-view v-if="enableScrollThrough" style="height: 200px;margin-bottom: 15px; padding: 10px;">
             <text class="container-content">{{ containerContent }}</text>
          </scroll-view>
          <text v-else class="container-content">{{ containerContent }}</text>
          <button @click="closeContainer" type="primary">关闭容器</button>
          <button class="mt-5" @click="navigateBack">后退页面</button>
        </view>
    </page-container>

    <!-- 多层堆叠弹层 - 第1层 -->
    <page-container :show="showStackedPop1" position="center" @afterleave="closeStackedLayer1">
      <view class="stacked-container" style="background-color: #fff; width: 360px;">
        <text class="container-title">第1层弹层</text>
        <text class="container-content">这是第1层弹层,点击下方按钮可以打开第2层</text>
        <view style="flex-direction: row; justify-content: space-around;">
          <button @click="showStackedLayer2" size="default" type="primary">打开第2层</button>
          <button @click="closeStackedLayer1" size="default">关闭本层</button>
        </view>
      </view>
    </page-container>

    <!-- 多层堆叠弹层 - 第2层 -->
    <page-container :show="showStackedPop2" position="center" @afterleave="closeStackedLayer2">
      <view class="stacked-container" style="background-color: #f0f0f0; width: 340px;">
        <text class="container-title">第2层弹层</text>
        <text class="container-content">这是第2层弹层,点击下方按钮可以打开第3层</text>
        <view style="flex-direction: row; justify-content: space-around;">
          <button @click="showStackedLayer3" size="default" type="primary">打开第3层</button>
          <button @click="closeStackedLayer2" size="default">关闭本层</button>
        </view>
      </view>
    </page-container>

    <!-- 多层堆叠弹层 - 第3层 -->
    <page-container :show="showStackedPop3" position="center" @afterleave="closeStackedLayer3">
      <view class="stacked-container" style="background-color: #e0e0e0; width: 300px;">
        <text class="container-title">第3层弹层</text>
        <text class="container-content">这是第3层弹层,最顶层的弹层</text>
        <button @click="closeStackedLayer3" size="default">关闭本层</button>
      </view>
    </page-container>
  </scroll-view>
</template>

<script setup lang="uts">
  const containerShow = ref<boolean>(false)
  const containerRound = ref<boolean>(false)
  const containerPosition = ref<string>('bottom')
  const containerOverlay = ref<boolean>(true)
  const containerTitle = ref<string>('Page-Container')
  const containerOverlayStyle = ref<string>('')
  const containerContent = ref<string>('这是一个 page-container 容器')
  const containerCloseOnSlideDown = ref<boolean>(false)
  const enableScrollThrough = ref<boolean>(false)

  // 多层堆叠弹层状态
  const showStackedPop1 = ref<boolean>(false)
  const showStackedPop2 = ref<boolean>(false)
  const showStackedPop3 = ref<boolean>(false)

  type Data = {
    onAfterLeaveCallCount: number
  }

  // 仅用于自动化测试
  const data = reactive<Data>({
    onAfterLeaveCallCount: 0
  })

  function resetConfig() {
    containerRound.value = false
    containerPosition.value = 'bottom'
    containerOverlay.value = true
    containerCloseOnSlideDown.value = false
    containerOverlayStyle.value = ''
    enableScrollThrough.value = false
  }

  function showContainer(position : string, text: string) {
    resetConfig()
    containerPosition.value = position
    containerShow.value = true
    containerTitle.value = `Position: ${position}`
    containerContent.value = `容器从 ${text} 弹出`
  }

  function showRound() {
    resetConfig()
    containerRound.value = true
    containerShow.value = true
    containerTitle.value = 'Round: true'
    containerContent.value = '弹窗圆角: true'
  }

  function showOverlay(overlay : boolean) {
    resetConfig()
    containerOverlay.value = overlay
    containerShow.value = true
    containerTitle.value = `Overlay: ${overlay}`
    containerContent.value = `遮罩层: ${overlay}`
  }

  function showTransparentOverlay() {
    resetConfig()
    containerOverlay.value = true
    containerOverlayStyle.value = 'background-color: rgba(0, 0, 0, 0);'
    containerTitle.value = '透明蒙层'
    containerContent.value = '蒙层开启但完全透明,可以点击蒙层区域关闭'
    containerShow.value = true
  }

  function showGreenTransparentOverlay() {
    resetConfig()
    containerOverlay.value = true
    containerPosition.value = 'center'
    containerOverlayStyle.value = 'background-color: rgba(76, 175, 80, 0.3);'
    containerTitle.value = '绿色半透明蒙层'
    containerContent.value = '蒙层开启但为绿色半透明,可以点击蒙层区域关闭'
    containerShow.value = true
  }

  function showSlideDown() {
    resetConfig()
    containerCloseOnSlideDown.value = true
    containerShow.value = true
    containerPosition.value = 'bottom'
    containerTitle.value = 'Close-on-slide-down: true'
    containerContent.value = '下滑关闭: true'
  }

  function autoClose() {
    resetConfig()
    containerShow.value = true
    containerPosition.value = 'bottom'
    containerTitle.value = 'Page-container'
    containerContent.value = '容器会在 2s 后自动关闭'
    setTimeout(() => {
      containerShow.value = false
    }, 2000)
  }

  function showScrollThrough() {
    resetConfig()
    enableScrollThrough.value = true
    containerShow.value = true
    containerPosition.value = 'bottom'
    containerTitle.value = 'Page-container'
    containerContent.value = `这是一个可滚动的内容区域。\n\n请向上或向下滚动此区域。\n\n当滚动到顶部或底部边界时\n\n测试主页面是否会跟随滚动(滚动穿透问题)\n\n理想情况下,当弹层内的scroll-view滚动到边界时,不应该触发主页面的滚动。\n\n已到达底部,现在可以测试向上滚动到顶部的情况。`
  }

  function onAfterLeave() {
    containerShow.value = false
    data.onAfterLeaveCallCount += 1
  }

  function closeContainer() {
    containerShow.value = false
  }

  function navigateBack() {
    uni.navigateBack()
  }

  // 多层堆叠弹层方法
  function showStackedLayer1() {
    showStackedPop1.value = true
  }

  function showStackedLayer2() {
    showStackedPop2.value = true
  }

  function showStackedLayer3() {
    showStackedPop3.value = true
  }

  function closeStackedLayer1() {
    showStackedPop1.value = false
  }

  function closeStackedLayer2() {
    showStackedPop2.value = false
  }

  function closeStackedLayer3() {
    showStackedPop3.value = false
  }

  defineExpose({
    data,
    showContainer,
    navigateBack,
    closeContainer
  })
</script>

<style scoped>
  .container {
    padding: 20px;
    background-color: #ffffff;
    min-height: 300px;
    min-width: 300px;
  }

  .container-title {
    font-size: 18px;
    font-weight: bold;
    color: #333;
    margin-bottom: 10px;
  }

  .container-content {
    font-size: 14px;
    color: #666;
    margin-bottom: 15px;
  }

  .mt-5 {
    margin-top: 5px;
  }

  .slider-down-info {
    font-size: 12px;
    color: #999;
    margin-top: 5px;
    margin-bottom: 20px;
  }

  .stacked-container {
    padding: 10px;
    border-radius: 8px;
    min-height: 150px;
  }
</style>

# 参见