custom-popup.vue 4.6 KB

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