前端筆記:基于Dialog自定義實(shí)現(xiàn)類(lèi)似抽屜效果
今天在維護(hù)vue2框架的一個(gè)業(yè)務(wù)系統(tǒng)遇到這樣一個(gè)問(wèn)題,有一個(gè)需求需要實(shí)現(xiàn)類(lèi)似于購(gòu)物車(chē)的效果,因?yàn)楸旧砜蚣苁褂玫氖荅lementUI框架,首先想到的就是使用官方提供的el-drawer。
圖片
所以按照官方文檔準(zhǔn)備在現(xiàn)有系統(tǒng)寫(xiě)一個(gè)Demo,寫(xiě)完之后調(diào)試了半個(gè)小時(shí)。始終無(wú)法實(shí)現(xiàn)抽屜的效果。最后看了下elementui的版本是2.10.1,最后發(fā)現(xiàn)這個(gè)版本是不支持抽屜組件的。
圖片
考慮到屬于老項(xiàng)目并且一直運(yùn)行比較穩(wěn)定,如果貿(mào)然升級(jí)elementui基礎(chǔ)框架的話(huà)風(fēng)險(xiǎn)還是非常大的。最后決定基于ElementUI的Dialog組件自定義的方式來(lái)實(shí)現(xiàn)抽屜的效果。下面給大家分享具體實(shí)現(xiàn)的過(guò)程,感興趣的前端朋友可以看一看!
二、代碼介紹
這部分相對(duì)比較簡(jiǎn)單,直接使用Dialog組件的基本結(jié)構(gòu),然后通過(guò)自定義模板實(shí)現(xiàn)類(lèi)似抽屜的標(biāo)題欄和內(nèi)容區(qū)域。
2.1 CSS部分
這部分是核心,需要定位對(duì)話(huà)框位置在頁(yè)面最右側(cè),并且實(shí)現(xiàn)類(lèi)似于抽屜從右向左滑動(dòng)的效果。
定位與尺寸
- 使用
position: fixed將Dialog固定到視口右側(cè) - 設(shè)置
height: 100vh實(shí)現(xiàn)全高效果 - 通過(guò)
right: 0和top: 0定位到右上角 - 去除默認(rèn)的圓角(
border-radius: 0)和邊距(margin: 0)
動(dòng)畫(huà)效果
頁(yè)面初始狀態(tài)使用transform: translateX(100%)將Dialog對(duì)話(huà)框隱藏在右側(cè)之外,需要打開(kāi)對(duì)話(huà)框的時(shí)候通過(guò)CSS transition實(shí)現(xiàn)平滑的滑入效果。
.drawer-dialog { transform: translateX(100%); transition: transform 0.3s ease-out;}.drawer-dialog.dialog-fade-enter-active { transform: translateX(0);}
.drawer-dialog {
transform: translateX(100%);
transition: transform 0.3s ease-out;
}
.drawer-dialog.dialog-fade-enter-active {
transform: translateX(0);
}2.2 JS部分
主要是實(shí)現(xiàn)抽屜的展示、隱藏邏輯、狀態(tài)管理、計(jì)算抽屜的寬度功能。
2.3 完整的代碼示例
<!DOCTYPE html><html lang="zh-CN"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>右側(cè)滑出抽屜組件</title> <link rel="stylesheet" > <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> <script src="https://unpkg.com/element-ui/lib/index.js"></script> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Helvetica Neue', Arial, sans-serif; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); min-height: 100vh; display: flex; flex-direction: column; align-items: center; padding: 40px 20px; color: #2c3e50; } .container { max-width: 800px; width: 100%; text-align: center; } h1 { margin-bottom: 20px; font-weight: 500; } .description { margin-bottom: 30px; color: #5e6d82; line-height: 1.6; } .control-panel { background: white; border-radius: 8px; padding: 25px; margin-bottom: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } .button-group { display: flex; justify-content: center; gap: 15px; margin-bottom: 20px; flex-wrap: wrap; } .demo-content { background: white; border-radius: 8px; padding: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); margin-bottom: 30px; } .demo-content p { margin-bottom: 15px; color: #5e6d82; } .status-info { margin-top: 20px; padding: 12px; background: #f0f9ff; border-radius: 4px; border-left: 4px solid #409EFF; } /* 抽屜組件樣式 */ .slide-drawer-mask { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 9998; } .slide-drawer { position: fixed; top: 0; right: 0; height: 100%; background: white; box-shadow: -2px 0 12px rgba(0, 0, 0, 0.15); display: flex; flex-direction: column; z-index: 9999; } .slide-drawer-header { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid #e6e9ed; background-color: #f5f7fa; } .slide-drawer-title { font-size: 16px; font-weight: 600; } .slide-drawer-close { cursor: pointer; color: #909399; font-size: 16px; padding: 5px; } .slide-drawer-close:hover { color: #409EFF; } .slide-drawer-body { padding: 20px; flex: 1; overflow-y: auto; } /* 過(guò)渡動(dòng)畫(huà) */ .fade-enter-active, .fade-leave-active { transition: opacity 0.3s; } .fade-enter, .fade-leave-to { opacity: 0; } .slide-right-enter-active, .slide-right-leave-active { transition: transform 0.3s ease-out; } .slide-right-enter, .slide-right-leave-to { transform: translateX(100%); } /* 響應(yīng)式設(shè)計(jì) */ @media (max-width: 768px) { .button-group { flex-direction: column; align-items: center; } .slide-drawer { width: 85% !important; } } </style></head><body> <div id="app"> <div class="container"> <h1>右側(cè)滑出抽屜組件</h1> <div class="control-panel"> <div class="button-group"> <el-button type="primary" @click="openDrawer">打開(kāi)抽屜</el-button> <el-button type="success" @click="openDrawer(0.25)">25%寬度</el-button> <el-button type="warning" @click="openDrawer(0.5)">50%寬度</el-button> <el-button type="danger" @click="openDrawer(0.75)">75%寬度</el-button> </div> </div> <div class="demo-content"> <p>這是一個(gè)使用封裝好的右側(cè)抽屜組件示例。</p> <p>組件采用簡(jiǎn)潔的設(shè)計(jì)風(fēng)格,支持自定義寬度和內(nèi)容。</p> <el-button type="text" @click="openDrawer">立即嘗試打開(kāi)抽屜</el-button> <div class="status-info" v-if="statusMessage"> {{ statusMessage }} </div> </div> </div> <!-- 抽屜組件 --> <slide-drawer :visible="drawerVisible" :width-ratio="drawerRatio" title="右側(cè)抽屜" @close="handleClose"> <div class="drawer-content"> <p>這是從右側(cè)滑出的抽屜內(nèi)容</p> <p>當(dāng)前寬度: {{ Math.round(drawerRatio * 100) }}%</p> <p>您可以在這里放置任何需要的內(nèi)容</p> <el-divider></el-divider> <p>支持各種HTML元素和組件</p> </div> </slide-drawer> </div> <script> // 定義抽屜組件 Vue.component('slide-drawer', { props: { visible: Boolean, title: { type: String, default: '抽屜默認(rèn)標(biāo)題' }, widthRatio: { type: Number, default: 0.3, validator: value => value > 0 && value <= 1 }, customStyle: { type: Object, default: function() { return {}; } } }, computed: { drawerStyle() { return { width: `${this.widthRatio * 100}%`, ...this.customStyle }; } }, methods: { close() { this.$emit('close'); }, handleMaskClick() { this.close(); } }, template: ` <div v-if="visible"> <transition name="fade"> <div class="slide-drawer-mask" @click="handleMaskClick"></div> </transition> <transition name="slide-right"> <div class="slide-drawer" :style="drawerStyle"> <div class="slide-drawer-header"> <span class="slide-drawer-title">{{ title }}</span> <span class="slide-drawer-close" @click="close"> <i class="el-icon-close"></i> </span> </div> <div class="slide-drawer-body"> <slot></slot> </div> </div> </transition> </div> ` }); new Vue({ el: '#app', data() { return { drawerVisible: false, drawerRatio: 0.3, statusMessage: '' }; }, methods: { openDrawer(ratio) { if (ratio) { this.drawerRatio = ratio; } this.drawerVisible = true; }, handleClose() { this.drawerVisible = false; this.statusMessage = '抽屜已關(guān)閉'; setTimeout(() => { this.statusMessage = ''; }, 2000); } } }); </script></body></html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>右側(cè)滑出抽屜組件</title>
<link rel="stylesheet" >
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg,
#f5f7fa
0%,
#c3cfe2
100%);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 40px 20px;
color:
#2c3e50
;
}
.container {
max-width: 800px;
width: 100%;
text-align: center;
}
h1 {
margin-bottom: 20px;
font-weight: 500;
}
.description {
margin-bottom: 30px;
color:
#5e6d82
;
line-height: 1.6;
}
.control-panel {
background: white;
border-radius: 8px;
padding: 25px;
margin-bottom: 30px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.button-group {
display: flex;
justify-content: center;
gap: 15px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.demo-content {
background: white;
border-radius: 8px;
padding: 30px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
}
.demo-content p {
margin-bottom: 15px;
color:
#5e6d82
;
}
.status-info {
margin-top: 20px;
padding: 12px;
background:
#f0f9ff
;
border-radius: 4px;
border-left: 4px solid
#409EFF
;
}
/* 抽屜組件樣式 */
.slide-drawer-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9998;
}
.slide-drawer {
position: fixed;
top: 0;
right: 0;
height: 100%;
background: white;
box-shadow: -2px 0 12px rgba(0, 0, 0, 0.15);
display: flex;
flex-direction: column;
z-index: 9999;
}
.slide-drawer-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid
#e6e9ed
;
background-color:
#f5f7fa
;
}
.slide-drawer-title {
font-size: 16px;
font-weight: 600;
}
.slide-drawer-close {
cursor: pointer;
color:
#909399
;
font-size: 16px;
padding: 5px;
}
.slide-drawer-close:hover {
color:
#409EFF
;
}
.slide-drawer-body {
padding: 20px;
flex: 1;
overflow-y: auto;
}
/* 過(guò)渡動(dòng)畫(huà) */
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
.slide-right-enter-active, .slide-right-leave-active {
transition: transform 0.3s ease-out;
}
.slide-right-enter, .slide-right-leave-to {
transform: translateX(100%);
}
/* 響應(yīng)式設(shè)計(jì) */
@media (max-width: 768px) {
.button-group {
flex-direction: column;
align-items: center;
}
.slide-drawer {
width: 85% !important;
}
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<h1>右側(cè)滑出抽屜組件</h1>
<div class="control-panel">
<div class="button-group">
<el-button type="primary" @click="openDrawer">打開(kāi)抽屜</el-button>
<el-button type="success" @click="openDrawer(0.25)">25%寬度</el-button>
<el-button type="warning" @click="openDrawer(0.5)">50%寬度</el-button>
<el-button type="danger" @click="openDrawer(0.75)">75%寬度</el-button>
</div>
</div>
<div class="demo-content">
<p>這是一個(gè)使用封裝好的右側(cè)抽屜組件示例。</p>
<p>組件采用簡(jiǎn)潔的設(shè)計(jì)風(fēng)格,支持自定義寬度和內(nèi)容。</p>
<el-button type="text" @click="openDrawer">立即嘗試打開(kāi)抽屜</el-button>
<div class="status-info" v-if="statusMessage">
{{ statusMessage }}
</div>
</div>
</div>
<!-- 抽屜組件 -->
<slide-drawer
:visible="drawerVisible"
:width-ratio="drawerRatio"
title="右側(cè)抽屜"
@close="handleClose">
<div class="drawer-content">
<p>這是從右側(cè)滑出的抽屜內(nèi)容</p>
<p>當(dāng)前寬度: {{ Math.round(drawerRatio * 100) }}%</p>
<p>您可以在這里放置任何需要的內(nèi)容</p>
<el-divider></el-divider>
<p>支持各種HTML元素和組件</p>
</div>
</slide-drawer>
</div>
<script>
// 定義抽屜組件
Vue.component('slide-drawer', {
props: {
visible: Boolean,
title: {
type: String,
default: '抽屜默認(rèn)標(biāo)題'
},
widthRatio: {
type: Number,
default: 0.3,
validator: value => value > 0 && value <= 1
},
customStyle: {
type: Object,
default: function() {
return {};
}
}
},
computed: {
drawerStyle() {
return {
width: `${this.widthRatio * 100}%`,
...this.customStyle
};
}
},
methods: {
close() {
this.$emit('close');
},
handleMaskClick() {
this.close();
}
},
template: `
<div v-if="visible">
<transition name="fade">
<div class="slide-drawer-mask" @click="handleMaskClick"></div>
</transition>
<transition name="slide-right">
<div class="slide-drawer" :style="drawerStyle">
<div class="slide-drawer-header">
<span class="slide-drawer-title">{{ title }}</span>
<span class="slide-drawer-close" @click="close">
<i class="el-icon-close"></i>
</span>
</div>
<div class="slide-drawer-body">
<slot></slot>
</div>
</div>
</transition>
</div>
`
});
new Vue({
el: '
#app
',
data() {
return {
drawerVisible: false,
drawerRatio: 0.3,
statusMessage: ''
};
},
methods: {
openDrawer(ratio) {
if (ratio) {
this.drawerRatio = ratio;
}
this.drawerVisible = true;
},
handleClose() {
this.drawerVisible = false;
this.statusMessage = '抽屜已關(guān)閉';
setTimeout(() => {
this.statusMessage = '';
}, 2000);
}
}
});
</script>
</body>
</html>2.4 運(yùn)行效果
為了方面可以直接運(yùn)行。這里直接采用了CDN方式引入js和css。復(fù)制代碼后可以直接保存為html文件然后直接瀏覽器打開(kāi)。預(yù)覽效果如下:
圖片
首先測(cè)試點(diǎn)擊25%寬度;
圖片
然后測(cè)試點(diǎn)擊75%寬度。
圖片
三、總結(jié)
以上功能是一個(gè)相對(duì)比較簡(jiǎn)單的基于Dialog自定義實(shí)現(xiàn)類(lèi)似抽屜效果的實(shí)用案例。對(duì)于一些老項(xiàng)目無(wú)法升級(jí)ElementUI基礎(chǔ)框架且美觀(guān)和業(yè)務(wù)相對(duì)簡(jiǎn)單的情況下還是值得推薦嘗試的一種方案。如果是新項(xiàng)目推薦直接升級(jí)框架的方式最靠譜。官方提供的el-drawer才是最優(yōu)的選擇。



























