custom-popup.vue 4.6 KB

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