惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Application and Cybersecurity Blog
Application and Cybersecurity Blog
A
About on SuperTechFans
S
SegmentFault 最新的问题
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Help Net Security
Help Net Security
有赞技术团队
有赞技术团队
博客园 - 【当耐特】
O
OpenAI News
美团技术团队
月光博客
月光博客
Apple Machine Learning Research
Apple Machine Learning Research
Schneier on Security
Schneier on Security
Webroot Blog
Webroot Blog
Cyberwarzone
Cyberwarzone
Hacker News - Newest:
Hacker News - Newest: "LLM"
Google Online Security Blog
Google Online Security Blog
T
Tenable Blog
S
Security Affairs
博客园_首页
S
Schneier on Security
Security Latest
Security Latest
T
Threat Research - Cisco Blogs
T
Tailwind CSS Blog
大猫的无限游戏
大猫的无限游戏
Spread Privacy
Spread Privacy
量子位
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
K
Kaspersky official blog
Hugging Face - Blog
Hugging Face - Blog
TaoSecurity Blog
TaoSecurity Blog
博客园 - 聂微东
Vercel News
Vercel News
M
MIT News - Artificial intelligence
T
Troy Hunt's Blog
B
Blog
MongoDB | Blog
MongoDB | Blog
Martin Fowler
Martin Fowler
Attack and Defense Labs
Attack and Defense Labs
L
LINUX DO - 最新话题
D
DataBreaches.Net
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Stack Overflow Blog
Stack Overflow Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
博客园 - Franky
W
WeLiveSecurity
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
F
Fortinet All Blogs
www.infosecurity-magazine.com
www.infosecurity-magazine.com
C
Check Point Blog
H
Hacker News: Front Page

博客园 - Liaofy

前端新范式:用 AI 提效开发,用 E2E 保证迭代质量 把SPA的加载速度提升一倍,有没有搞头? 使用mumu模拟器抓包 andriod app 如何在本地给 vue-router4 扩展功能? Vue3 路由优化,使页面初次渲染效率翻倍 【eslint 插件开发】禁用 location 跳转外部链接 vue 项目使用 charles 代理线上页面到本地后显示404 避免使用单个数字作为对象的key 轻松搞懂POST与PUT的区别 vite(or rollup) 实现 webpackChunkName webapp项目架构畅想(vue3).md vite试玩:老项目启动从6分钟到秒开 修剪AST树减少webapck打包尺寸 【uniapp】改善中大型uniapp小程序项目开发体验 vscode 调试 webpack 打包 5分钟,搞清 Unicode 与 UTF-8 的区别 deAsync.js 真正让异步变同步 盘的转——使用缓动函数完成动画 多个请求间断失败,如何优雅处理?
高扩展弹出层组件设计实现(H5&小程序)
Liaofy · 2022-07-11 · via 博客园 - Liaofy

背景

随着业务的发展,弹窗逐渐替代翻页,承载越来越多的用户需求。由于没有统一、好用的公共弹窗组件,业务同学通常会编写属于自己的弹窗,这造成了一些问题:

  • 用户体验不一致
  • 大量重复代码,不易维护和升级
  • 弹窗滚动穿透
  • 未设置 iPhone 手机底部安全区域

我们急切的需要提供一个公共的弹窗组件,不仅要解决以上问题,还必须满足各种业务需求。

设计分析

分析过往的 UI,最常用的弹窗有下面两种:


由此可以得出如下结论:

  • 组成:弹窗由蒙层和容器组成
  • 位置:容器集中在底部和中间位置,可扩展为:上、右、下、左、中五个位置
  • 容器内容:标题区、内容区、底部区组成
    • 标题:可选,允许显示关闭按钮
    • 内容:自定义,允许滚动
    • 底部:可选

设计实现

解决滚动穿透问题

打开弹窗,给body设置类名hl-overflow-hidden,关闭弹窗则移除。

document.body.classList.add('hl-overflow-hidden');
document.body.classList.remove('hl-overflow-hidden');


.hl-overflow-hidden {
  overflow: hidden!important;
}

解决蒙层滚动穿透,核心在于设置touchmove

<div @touchmove="preventDefault"></div>
function preventDefault(event) {
  if (typeof event.cancelable !== 'boolean' || event.cancelable) {
    event.preventDefault();
  }
  event.stopPropagation()
}

高度自定义容器设计

将容器从上到下分为三个部分:head/body/foot。

head 区既可以便捷设置title,也可完全自定义。

body 区设置为默认插槽,根据内容高度自动设置滚动,并emit滚动事件。

foot 区由用户自定义,默认不展示。

<div class="huoli-popup fx-col" :style="{ height }">## 背景
随着业务的发展,弹窗逐渐替代翻页,承载越来越多的用户需求。由于没有统一、好用的公共弹窗组件,业务同学通常会编写属于自己的弹窗,这造成了一些问题:
* 用户体验不一致
* 大量重复代码,不易维护和升级
* 弹窗滚动穿透
* 未设置 iPhone 手机底部安全区域

我们急切的需要提供一个公共的弹窗组件,不仅要解决以上问题,还必须满足各种业务需求。

## 设计分析
分析过往的 UI,最常用的弹窗有下面两种:

![](https://img2022.cnblogs.com/blog/1085489/202207/1085489-20220711111050894-1485340801.png)
![](https://img2022.cnblogs.com/blog/1085489/202207/1085489-20220711111059041-1856516507.png)

由此可以得出如下结论:
* 组成:弹窗由蒙层和容器组成
* 位置:容器集中在底部和中间位置,可扩展为:上、右、下、左、中五个位置
* 容器内容:标题区、内容区、底部区组成
  * 标题:可选,允许显示关闭按钮
  * 内容:自定义,允许滚动
  * 底部:可选

## 设计实现
### 解决滚动穿透问题
打开弹窗时,给body设置类名`hl-overflow-hidden`,关闭弹窗则移除。
````js
document.body.classList.add('hl-overflow-hidden');
document.body.classList.remove('hl-overflow-hidden');


.hl-overflow-hidden {
  overflow: hidden!important;
}

解决小程序滚动穿透问题

完整实现见popup4mini.

和H5不同,小程序中,我们给容器增加了@touchmove.stop="noop",禁止了touchmove事件,然后在滚动区域使用小程序ScrollView组件。

  <div :class="['hl-popup fx-col', hlPopupClasses]" :style="style" @touchmove.stop="noop">
    <div class="hl-popup-hd"></div>

    <ScrollView scrollY class="hl-popup-bd" @scroll="onScroll">
      <slot></slot>
    </ScrollView>

    <slot name="foot"></slot>
  </div>

  function noop() {}

高度自定义容器设计

将容器从上到下分为三个部分:head/body/foot。

head 区既可以便捷设置title,也可完全自定义。

body 区设置为默认插槽,根据内容高度自动设置滚动,并emit滚动事件。

foot 区由用户自定义,默认不展示。

<div class="huoli-popup fx-col" :style="{ height }">
  <div class="huoli-popup-hd">
    <template v-if="title">{{title}}</template>
    <slot v-else name="title"></slot>
    <img v-if="closable" @click="onClickClose" />
  </div>

  <div class="huoli-popup-bd" @scroll="onScroll">
    <slot></slot>
  </div>

  <div class="huoli-popup-foot"><slot name="foot"></slot></div>
</div>

无依赖简单实现

详见

结合 vant 使用

<template>
  <Popup 
    v-model="show"
    position="bottom"
    round
    :style="{ height, overflow: 'hidden' }"
    get-container="body"
    @close="onClickClose"
  >
    <div class="huoli-popup fx-col">
      <div class="huoli-popup-hd">
        <slot v-else name="title"></slot>
        <img v-if="closable" class="huoli-popup-close-icon" @click="onClickClose" />
      </div>
      <div class="huoli-popup-bd" @scroll="onScroll">
        <slot></slot>
      </div>
      <div class="huoli-popup-foot"><slot name="foot"></slot></div>
    </div>
  </Popup>
</template>

<script>
import { Popup } from "huoli-ui"
export default {
  name: 'huoli-popup',
  props: {
    show: {
      type: Boolean,
      default: false,
    },
    closable: {
      type: Boolean,
      default: false,
    },
    height: {
      type: String,
      default: ',
    },
  },
  components: {
    Popup
  },
  methods: {
    onClickClose() {
      this.$emit('hide');
    },
    onScroll: throttle(function(e) {
      this.$emit('scroll', e)
    })
  }
}
function throttle(func, wait = 50, immediate) {
  let timeout
  return function() {
    const context = this;
    const args = arguments;
    if (immediate) {
      func.apply(context, args);
      immediate = false
    }

    if (timeout) return

    timeout = setTimeout(function() {
      timeout = null;
      func.apply(context, args)
    }, wait);
  }
}
</script>

<style lang="scss">
.huoli-popup {
  height: 100%;
  .huoli-popup-hd {
    min-height: 48px;
    width: 100%;
    background: inherit;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;

    font-size: 16px;
    font-weight: 400;
    color: #262626;
    line-height: 20px;
  }
  .huoli-popup-close-icon {
    position: absolute;
    width: 16px;
    height: 16px;
    right: 16px;
    top: 16px;
  }
  .huoli-popup-bd {
    flex: 1;
    box-sizing: border-box;
    overflow-y: auto;
  }
}
</style>


## 无依赖简单实现

[详见](https://gist.github.com/CaptainLiao/204e93fdfacc0ca60b3f386191216785)

## 结合 vant 使用
````vue
<template>
  <Popup 
    v-model="show"
    position="bottom"
    round
    :style="{ height, overflow: 'hidden' }"
    get-container="body"
    @close="onClickClose"
  >
    <div class="huoli-popup fx-col">
      <div class="huoli-popup-hd">
        <slot v-else name="title"></slot>
        <img v-if="closable" src="./img/close@2x.png" class="huoli-popup-close-icon" @click="onClickClose" />
      </div>
      <div class="huoli-popup-bd" @scroll="onScroll">
        <slot></slot>
      </div>
      <div class="huoli-popup-foot"><slot name="foot"></slot></div>
    </div>
  </Popup>
</template>

<script>
import { Popup } from "huoli-ui"
export default {
  name: 'huoli-popup',
  props: {
    show: {
      type: Boolean,
      default: false,
    },
    closable: {
      type: Boolean,
      default: false,
    },
    height: {
      type: String,
      default: ',
    },
  },
  components: {
    Popup
  },
  methods: {
    onClickClose() {
      this.$emit('hide');
    },
    onScroll: throttle(function(e) {
      this.$emit('scroll', e)
    })
  }
}
function throttle(func, wait = 50, immediate) {
  let timeout
  return function() {
    const context = this;
    const args = arguments;
    if (immediate) {
      func.apply(context, args);
      immediate = false
    }

    if (timeout) return

    timeout = setTimeout(function() {
      timeout = null;
      func.apply(context, args)
    }, wait);
  }
}
</script>

<style lang="scss">
.huoli-popup {
  height: 100%;
  .huoli-popup-hd {
    min-height: 48px;
    width: 100%;
    background: inherit;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;

    font-size: 16px;
    font-weight: 400;
    color: #262626;
    line-height: 20px;
  }
  .huoli-popup-close-icon {
    position: absolute;
    width: 16px;
    height: 16px;
    right: 16px;
    top: 16px;
  }
  .huoli-popup-bd {
    flex: 1;
    box-sizing: border-box;
    overflow-y: auto;
  }
}
</style>