custom-popup.vue 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. <template>
  2. <view class="custom-popup" :class="{'custom-popup--show': showPopup}" :style="{ zIndex: zIndex }">
  3. <!-- 遮罩层 -->
  4. <view
  5. v-if="mask"
  6. class="custom-popup__mask"
  7. :style="{
  8. backgroundColor: maskBackgroundColor,
  9. zIndex: zIndex
  10. }"
  11. :class="{'custom-popup__mask--show': showPopup}"
  12. @tap.stop="maskClick"
  13. @touchmove.stop.prevent="clear"
  14. ></view>
  15. <!-- 弹出层 -->
  16. <view
  17. class="custom-popup__container"
  18. :class="[
  19. `custom-popup__container--${mode}`,
  20. showPopup && `custom-popup__container--${mode}--active`,
  21. ]"
  22. :style="{
  23. zIndex: zIndex + 1,
  24. backgroundColor: bgColor
  25. }"
  26. @tap.stop
  27. @touchmove.stop.prevent="clear"
  28. >
  29. <!-- 顶部安全区适配 -->
  30. <view class="custom-popup__safe-area-inset-top" v-if="safeAreaInsetTop"></view>
  31. <!-- 主体内容 -->
  32. <view
  33. class="custom-popup__content"
  34. :class="{'custom-popup__content--round': round}"
  35. :style="{
  36. width: width ? width : 'auto',
  37. borderRadius: mode === 'center' ? borderRadius : 0
  38. }"
  39. >
  40. <slot></slot>
  41. </view>
  42. <!-- 底部安全区适配 -->
  43. <view class="custom-popup__safe-area-inset-bottom" v-if="safeAreaInsetBottom"></view>
  44. </view>
  45. </view>
  46. </template>
  47. <script>
  48. export default {
  49. name: 'CustomPopup',
  50. model: {
  51. prop: 'value',
  52. event: 'input'
  53. },
  54. props: {
  55. // 是否显示弹窗 (v-model绑定)
  56. value: {
  57. type: Boolean,
  58. default: false
  59. },
  60. // 弹出方式
  61. // top: 顶部弹出
  62. // bottom: 底部弹出
  63. // center: 中部弹出
  64. // left: 左侧弹出
  65. // right: 右侧弹出
  66. mode: {
  67. type: String,
  68. default: 'bottom'
  69. },
  70. // 是否显示遮罩
  71. mask: {
  72. type: Boolean,
  73. default: true
  74. },
  75. // 点击遮罩是否关闭弹窗
  76. maskClosable: {
  77. type: Boolean,
  78. default: true
  79. },
  80. // 弹窗背景色
  81. bgColor: {
  82. type: String,
  83. default: '#ffffff'
  84. },
  85. // 弹窗圆角
  86. borderRadius: {
  87. type: [String, Number],
  88. default: '20rpx'
  89. },
  90. // 弹窗宽度,mode=center时生效
  91. width: {
  92. type: String,
  93. default: '650rpx'
  94. },
  95. // 是否显示圆角
  96. round: {
  97. type: Boolean,
  98. default: false
  99. },
  100. // z-index层级
  101. zIndex: {
  102. type: [String, Number],
  103. default: 10075
  104. },
  105. // 遮罩的背景色
  106. maskBackgroundColor: {
  107. type: String,
  108. default: 'rgba(0, 0, 0, 0.5)'
  109. },
  110. // 是否适配底部安全区
  111. safeAreaInsetBottom: {
  112. type: Boolean,
  113. default: true
  114. },
  115. // 是否适配顶部安全区
  116. safeAreaInsetTop: {
  117. type: Boolean,
  118. default: false
  119. }
  120. },
  121. data() {
  122. return {
  123. showPopup: false
  124. }
  125. },
  126. watch: {
  127. value(val) {
  128. this.showPopup = val;
  129. }
  130. },
  131. created() {
  132. this.showPopup = this.value;
  133. },
  134. methods: {
  135. // 点击遮罩
  136. maskClick() {
  137. if (this.maskClosable) {
  138. this.closePopup();
  139. }
  140. },
  141. // 关闭弹窗
  142. closePopup() {
  143. this.showPopup = false;
  144. this.$emit('input', false);
  145. this.$emit('close');
  146. },
  147. // 防止滚动穿透
  148. clear(e) {
  149. e.stopPropagation();
  150. e.preventDefault();
  151. }
  152. }
  153. }
  154. </script>
  155. <style lang="scss">
  156. .custom-popup {
  157. position: fixed;
  158. top: 0;
  159. left: 0;
  160. width: 100%;
  161. height: 100%;
  162. visibility: hidden;
  163. &__mask {
  164. position: fixed;
  165. top: 0;
  166. left: 0;
  167. bottom: 0;
  168. right: 0;
  169. opacity: 0;
  170. transition: opacity 0.3s;
  171. background-color: rgba(0, 0, 0, 0.5);
  172. z-index: 10075;
  173. &--show {
  174. opacity: 1;
  175. }
  176. }
  177. &__container {
  178. position: fixed;
  179. transition: all 0.3s ease-in-out;
  180. z-index: 10076;
  181. &--center {
  182. top: 50%;
  183. left: 50%;
  184. transform: translate(-50%, -50%) scale(0.9);
  185. opacity: 0;
  186. &--active {
  187. transform: translate(-50%, -50%) scale(1);
  188. opacity: 1;
  189. }
  190. }
  191. &--bottom {
  192. bottom: 0;
  193. left: 0;
  194. width: 100%;
  195. transform: translateY(100%);
  196. &--active {
  197. transform: translateY(0);
  198. }
  199. }
  200. &--top {
  201. top: 0;
  202. left: 0;
  203. width: 100%;
  204. transform: translateY(-100%);
  205. &--active {
  206. transform: translateY(0);
  207. }
  208. }
  209. &--left {
  210. top: 0;
  211. left: 0;
  212. height: 100%;
  213. transform: translateX(-100%);
  214. &--active {
  215. transform: translateX(0);
  216. }
  217. }
  218. &--right {
  219. top: 0;
  220. right: 0;
  221. height: 100%;
  222. transform: translateX(100%);
  223. &--active {
  224. transform: translateX(0);
  225. }
  226. }
  227. }
  228. &__content {
  229. position: relative;
  230. &--round {
  231. border-radius: 20rpx;
  232. &::after {
  233. content: "";
  234. position: absolute;
  235. top: 0;
  236. left: 0;
  237. width: 200%;
  238. height: 200%;
  239. transform: scale(0.5);
  240. transform-origin: 0 0;
  241. border-radius: 40rpx;
  242. pointer-events: none;
  243. }
  244. }
  245. }
  246. &__safe-area-inset-top {
  247. width: 100%;
  248. height: var(--status-bar-height);
  249. }
  250. &__safe-area-inset-bottom {
  251. width: 100%;
  252. height: env(safe-area-inset-bottom);
  253. }
  254. &--show {
  255. visibility: visible;
  256. }
  257. }
  258. </style>