本來是使用第三方庫 vant的vue 組件庫 的popup,後來在popup中使用better-scroll插件的時候,出現並不兼容的情況,也就自己搭建一個popu插件,中間遇到很多問題,都會記錄一下,給自己一些總結!
popup組件實現思路
首先是模仿vant的popup組件的功能:vant-popup
1.popup組件開發與樣式編寫(一個遮罩層,一個顯示層)。
2.組件引用(目前是選擇的組件導入的方法,沒有選擇使用插件this.$popup(),主要是用戶可以通過組件自定義傳參,插件的方式不好實現)。
代碼
popup.vue
<template>
<div >
<!-- 遮罩層-->
<transition name="gh-fade" >
<div class="gh-overlay "
v-if="isShow"
ref="overlay"
@click="changShowStat(false)"
style="z-index: 2019;"
:style="overlayStyle">
</div>
</transition>
<!-- 內容層-->
<transition :name="'gh-slide-'+position">
<div class="gh-popup "
v-if="isShow"
ref="popup"
:class="[
getPosition,
{'gh-popup--round':round}
]"
style="z-index:2020"
:style="styles">
<slot></slot>
<i v-if="closeable"
@click="changShowStat(false)"
tabindex="0"
:class="{
'gh-icon-cross': closeable && !closeIcon ,
}"
class="gh-icon gh-popup__close-icon gh-popup__close-icon--top-right">
<img v-if="closeIcon" class="gh-icon__image" :src="closeIcon" alt="">
</i>
</div>
</transition>
</div>
</template>
<script>
export default {
name: "Popup",
props:{
value:Boolean,
overlayStyle:{
type:Object
},
height:{
type:String,
default: 'none'
},
width:{
type:String,
default: 'none'
},
styles:{
type:Object
},
position:{
type:String
},
closeable:{
type:Boolean,
default:false
},
closeIcon:{
type:String
},
round:{
type:Boolean,
default:false
},
},
data(){
return{
isShow:this.value
}
},
mounted(){
this.$nextTick(() => {
const body = document.querySelector("body");
if (body.append) {
body.append(this.$el);
} else {
body.appendChild(this.$el);
}
});
},
beforeUpdate(){
},
computed:{
getPosition(){
return 'gh-popup--'+this.position
},
},
watch: {
// 監聽value的值更新
value(val) {
this.isShow = val;
}
},
methods:{
changShowStat(val){
this.$emit('input',false)
},
},
}
</script>
<style scoped>
.gh-overflow-hidden {
overflow: hidden !important;
}
.gh-overlay {
position: fixed;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,.7);
}
@keyframes fadeInUp {
from {
opacity: 0;
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
}
to {
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
}
.fadeInUp {
-webkit-animation-name: fadeInUp;
animation-name: fadeInUp;
animation-duration: 0.5s;
}
.gh-popup {
position: fixed;
max-height: 100%;
overflow-y: auto;
background-color: #fff;
}
.gh-popup--center {
top: 50%;
left: 50%;
-webkit-transform: translate3d(-50%, -50%, 0);
transform: translate3d(-50%, -50%, 0);
}
.gh-popup--top {
top: 0;
left: 0;
width: 100%;
}
.gh-popup--bottom {
bottom: 0;
left: 0;
width: 100%;
}
.gh-popup--left {
top: 50%;
left: 0;
-webkit-transform: translate3d(0, -50%, 0);
transform: translate3d(0, -50%, 0);
}
.gh-popup--right {
top: 50%;
right: 0;
-webkit-transform: translate3d(0, -50%, 0);
transform: translate3d(0, -50%, 0);
}
.gh-icon {
position: relative;
display: inline-block;
font: normal normal normal 14px/1 ght-icon;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
}
.gh-popup__close-icon {
position: absolute;
z-index: 1;
color: #c8c9cc;
font-size: 22px;
cursor: pointer;
}
.gh-popup__close-icon--top-right {
top: 16px;
right: 16px;
}
.gh-icon-cross::before {
content: "\2716";
}
.gh-icon__image {
width: 1em;
height: 1em;
object-fit: contain;
}
.gh-icon::before {
display: inline-block;
}
.gh-popup--round {
border-radius: 20px 20px 0 0;
}
/* 內容css start*/
@-webkit-keyframes gh-slide-bottom-enter {
from {
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
}
}
@keyframes gh-slide-bottom-enter {
from {
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
}
}
@-webkit-keyframes gh-slide-bottom-leave {
to {
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
}
}
@keyframes gh-slide-bottom-leave {
to {
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
}
}
@-webkit-keyframes gh-slide-top-enter {
from {
-webkit-transform: translate3d(0, -100%, 0);
transform: translate3d(0, -100%, 0);
}
}
@keyframes gh-slide-top-enter {
from {
-webkit-transform: translate3d(0, -100%, 0);
transform: translate3d(0, -100%, 0);
}
}
@-webkit-keyframes gh-slide-top-leave {
to {
-webkit-transform: translate3d(0, -100%, 0);
transform: translate3d(0, -100%, 0);
}
}
@keyframes gh-slide-top-leave {
to {
-webkit-transform: translate3d(0, -100%, 0);
transform: translate3d(0, -100%, 0);
}
}
@-webkit-keyframes gh-slide-left-enter {
from {
-webkit-transform: translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0);
}
}
@keyframes gh-slide-left-enter {
from {
-webkit-transform: translate3d(-100%, -50%, 0);
transform: translate3d(-100%, -50%, 0);
}
}
@-webkit-keyframes gh-slide-left-leave {
to {
-webkit-transform: translate3d(-100%, -50%, 0);
transform: translate3d(-100%, -50%, 0);
}
}
@keyframes gh-slide-left-leave {
to {
-webkit-transform: translate3d(-100%, -50%, 0);
transform: translate3d(-100%, -50%, 0);
}
}
@-webkit-keyframes gh-slide-right-enter {
from {
-webkit-transform: translate3d(100%, -50%, 0);
transform: translate3d(100%, -50%, 0);
}
}
@keyframes gh-slide-right-enter {
from {
-webkit-transform: translate3d(100%, -50%, 0);
transform: translate3d(100%, -50%, 0);
}
}
@-webkit-keyframes gh-slide-right-leave {
to {
-webkit-transform: translate3d(100%, -50%, 0);
transform: translate3d(100%, -50%, 0);
}
}
@keyframes gh-slide-right-leave {
to {
-webkit-transform: translate3d(100%, -50%, 0);
transform: translate3d(100%, -50%, 0);
}
}
@-webkit-keyframes gh-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes gh-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@-webkit-keyframes gh-fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes gh-fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@-webkit-keyframes gh-rotate {
from {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes gh-rotate {
from {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
.gh-fade-enter-active {
-webkit-animation: 0.5s gh-fade-in;
animation: 0.5s gh-fade-in;
}
.gh-fade-leave-active {
-webkit-animation: 0.5s gh-fade-out;
animation: 0.5s gh-fade-out;
}
.gh-slide-top-enter-active {
-webkit-animation: gh-slide-top-enter 0.5s both ease;
animation: gh-slide-top-enter 0.5s both ease;
}
.gh-slide-top-leave-active {
-webkit-animation: gh-slide-top-leave 0.5s both ease;
animation: gh-slide-top-leave 0.5s both ease;
}
.gh-slide-bottom-enter-active {
-webkit-animation: gh-slide-bottom-enter 0.5s both ease;
animation: gh-slide-bottom-enter 0.5s both ease;
}
.gh-slide-bottom-leave-active {
-webkit-animation: gh-slide-bottom-leave 1s both ease;
animation: gh-slide-bottom-leave 1s both ease;
}
.gh-slide-left-enter-active {
-webkit-animation: gh-slide-left-enter 0.5s both ease;
animation: gh-slide-left-enter 0.5s both ease;
}
.gh-slide-left-leave-active {
-webkit-animation: gh-slide-left-leave 0.5s both ease;
animation: gh-slide-left-leave 0.5s both ease;
}
.gh-slide-right-enter-active {
-webkit-animation: gh-slide-right-enter 0.5s both ease;
animation: gh-slide-right-enter 0.5s both ease;
}
.gh-slide-right-leave-active {
-webkit-animation: gh-slide-right-leave 0.5s both ease;
animation: gh-slide-right-leave 0.5s both ease;
}
</style>
組件導入
//導入popup組件
import Popup from "Popup";
//在components中註冊組件
components:{
Popup,
},
//組件的使用
<popup v-model="isShow" //用於控制組件是否顯示
position="bottom" //用於控制組件顯示位置
:styles="{height:'50%'}" //組件樣式
>
</popup>
組件設計到的功能點:
1.v-model 組件之間參數雙向綁定
watch: {
// 監聽value的值更新
value(val) {
this.isShow = val;
}
},
methods:{
//使用$emit對父組件的v-model,通過input進行數據綁定
changShowStat(val){
this.$emit('input',false)
},
},
2.組件自定義掛載(我這裏是掛載到了body下)
使用$nextTick函數,在組件更新時對組件進行自定義掛載,我看iview你們也有全局掛載,但實現思路不一樣,還在研究中。。
mounted(){
this.$nextTick(() => {
const body = document.querySelector("body");
if (body.append) {
body.append(this.$el);
} else {
body.appendChild(this.$el);
}
});
},
3.vue組件過渡動畫,因爲開始學vue沒多久,剛開始使用的js動態掛載css動畫,來實現,邏輯代碼寫了一大堆,中間遇到很多問題,比如在watch鉤子函數中,dom元素獲取不到問題,還爲此仔細學習了vue生命週期,最後實現了,還是出現點擊頻率過快,出現組件加載問題,說了很多廢話,最後學習了vue的過渡動畫 transition組件,來實現的。
樣式寫在vue組件的樣式裏面
參數props
參數 | 說明 | 類型 | 默認值 |
---|---|---|---|
v-model | 當前組件是否顯示 | boolean | false |
overlay | 是否顯示遮罩層 | boolean | true |
position | 彈出位置,可選值爲 top bottom right left |
string | center |
overlay-style | 自定義遮罩層樣式 | object | - |
round | 是否顯示圓角 | boolean | false |
styles | 內容層樣式 | boolean | true |
closeable | 是否顯示關閉圖標 | boolean | false |
close-icon | 關閉圖標名稱或圖片鏈接 | string | cross |
雖然花了3天時間學了不少東西:
1.v-model 組件間的參數雙向綁定
2.vue生命週期
3.vue $$nextTick函數
4.css3動畫
5.vue <transition>過渡組件
6.vue插件開發
7.第三方ui庫組件的引入
遇到的bug
1.props中數據只能單向傳輸,無法雙向綁定,不要在子組件中改變props參數
2.css加載順序問題,導致頁面效果出不來,加載順序是自上而下,渲染優先級是自下而上(類似java棧)
3.vantUI組件的popup不兼容better-scroll,所以才自己做了一個
4.頁面按鈕點擊無效,因爲使用了定位佈局,父組件沒有設置高度,導致無法點擊
5.當自定義動畫,使用js動態加載動畫時,因爲需要退出效果,往往在修改是否顯示的參數(value)的時候,需要延時0.5秒,等動畫顯示完成後,再修改value的值;這時候點擊過快會出現組件加載問題,考慮過使用防抖來優化,但是感覺治標不治本,後面採用了vue <transition>過渡組件。
methods:{
changShowStat(val){
if(val){
this.$emit('input',val)
}else{
setTimeout(() => {
this.$emit('input',val)
}, 500)
}
},
6.dom獲取不到問題,你會發現能打印this.$refs 能夠找到dom,但是就是去獲取this.$refs.xxx的時候,顯示undefined ,主要是 DOM結構已經出來了,但是如果在DOM結構中的某個DOM節點使用了v-if、v-show或者v-for(即根據獲得的後臺數據來動態操作DOM,即響應式),那麼這些DOM是不會再mounted階段找到的。參考資料,主要是dom沒用掛載 最後自己也詳細回顧了一下生命週期,覺得這個搞不懂,vue就算沒入門;
繼續學習!