智能客服
你问我答,随时在线为你解决问题
抽屉组件是一种特殊的弹出面板,可以模拟手机App中拉出、推入抽屉的效果。抽屉一般具有如下特点:
抽屉(drawer)组件结构分为控制器(controls)和抽屉内容(content)两部分。一般来说,controls都是按钮、图标之类的可点击的组件,类似真实抽屉的把手;content是抽屉内部的东西,每个抽屉的content都是不一样的。点击controls可以触发content的展开和收起。布局结构如下图:

布局代码如下:
- <div class="page">
- <div class="controls">
- <image></image>
- </div>
- <stack class="drawer_container">
- <div class="page_content">
- …
- </div>
- <drawer class="drawer">
- <div class="content">
- …
- </div>
- </drawer >
- </stack>
- </div>
抽屉外观都是通用的,但是抽屉内部格局content不一样,在设计的时候,不能把content布局固定,否则一旦content部分的UI有变化,会导致子组件也要修改,违背了代码设计中的“开闭”原则。
所以,在子组件drawer.ux中,使用slot 组件来承载父组件中定义的content,由使用drawer组件的页面来完成content布局,如下代码所示:
- <template>
- <div id="drawercontent" style="display:flex;position:absolute;width:100%;height:100%;top:0;left:0;bottom: 0; flex-direction: {{flexdirection}}" >
- <div class="{{maskstyle}}" onclick="close('mask')"></div>
- <div id="unidrawercontent" class="{{unidrawerstyle}}" style="width:{{drawerWidth}}+'px'}">
- <slot></slot>
- </div>
- </div>
- </template>
支持的属性:
属性 |
类型 |
默认值 |
描述 |
|---|---|---|---|
mode |
String |
left |
抽屉的显示位置,支持left和right。 |
width |
Number |
320px |
抽屉的宽度。 |
mask |
Boolean |
true |
抽屉展开时是否显示遮罩层。 |
maskClick |
Boolean |
true |
点击遮罩层是否关闭抽屉。 |
支持的事件:
事件名称 |
参数 |
描述 |
|---|---|---|
drawerchange |
{showDrawer:booleanValue} |
抽屉展开、收起的回调事件。 |
展开时,移到可视区域;收起时,移到屏幕之外。不管是展开还是收起,都是平滑的动画效果,代码如下。
左抽屉展开、收起动画:
- @keyframes translateX {
- from {
- transform: translateX(-110px);
- }
-
- to {
- transform: translateX(0px);
- }
- }
-
- @keyframes translateXReverse {
- from {
- transform: translateX(0px);
- }
-
- to {
- transform: translateX(-750px);
- }
- }
右抽屉展开、收起动画:
- @keyframes translateXRight {
- from {
- transform: translateX(300px);
- }
-
- to {
- transform: translateX(0px);
- }
- }
-
- @keyframes translateXRightReverse {
- from {
- transform: translateX(0px);
- }
-
- to {
- transform: translateX(750px);
- }
- }
- onInit() {
- console.info("drawer oninit");
- this.$on('broaddrawerstate', this.drawerStateEvt);
- this.drawerWidth = this.width;
- if(this.mode=="left"){
- this.flexdirection="row";
- }else{
- this.flexdirection="row-reverse";
- }
- this.$watch('mode', 'onDrawerModeChange');
- },
- .uni-drawer-open-left {
- display: flex;
- height: 100%;
- animation-name: translateX;
- animation-timing-function: linear;
- animation-fill-mode: forwards;
- animation-duration: 300ms;
- }
-
- .uni-drawer-closed-left {
- display: flex;
- height: 100%;
- animation-name: translateXReverse;
- animation-timing-function: linear;
- animation-fill-mode: forwards;
- animation-duration: 600ms;
- }
当设置flex-direction: row-reverse时,抽屉显示在右侧。右抽屉展开、收起的style代码如下:
- .uni-drawer-open-right {
- display: flex;
- height: 100%;
- flex-direction: row-reverse;
- animation-name: translateXRight;
- animation-timing-function: linear;
- animation-fill-mode: forwards;
- animation-duration: 300ms;
- }
-
- .uni-drawer-closed-right {
- display: flex;
- height: 100%;
- flex-direction: row-reverse;
- animation-name: translateXRightReverse;
- animation-timing-function: linear;
- animation-fill-mode: forwards;
- animation-duration: 600ms;
- }
遮罩层初始状态不显示,通过“display: none;”来隐藏。抽屉展示时,显示遮罩层,收起时不显示。遮罩层使用透明度实现,代码如下:
- .uni-mask-open {
- display: flex;
- height: 100%;
- width: 100%;
- position: absolute;
- background-color: rgb(0, 0, 0);
- opacity: 0.4;
- }
- .uni-mask-closed {
- height: 100%;
- width: 100%;
- position: absolute;
- background-color: rgb(0, 0, 0);
- display: none;
- }
通过监听touchstart和touchend事件,可以实现在屏幕边缘处手势滑动呼出抽屉。
抽屉drawer.ux代码:
- <template>
- <div id="drawercontent" style="display:flex;position:absolute;width:100%;height:100%;top:0;left:0;bottom: 0; flex-direction: {{flexdirection}}" onswipe="dealDrawerSwipe">
- <div class="{{maskstyle}}" onclick="close('mask')"></div>
- <div id="unidrawercontent" class="{{unidrawerstyle}}" style="width:{{drawerWidth}}+'px'}">
- <slot></slot>
- </div>
- </div>
- </template>
- <style>
- .stack {
- flex-direction: column;
- height: 100%;
- width: 100%;
- }
- .uni-mask-open {
- display: flex;
- height: 100%;
- width: 100%;
- position: absolute;
- background-color: rgb(0, 0, 0);
- opacity: 0.4;
- }
- .uni-mask-closed {
- height: 100%;
- width: 100%;
- position: absolute;
- background-color: rgb(0, 0, 0);
- display: none;
- }
- .uni-drawer {
- display: none;
- height: 100%;
- }
- .uni-drawer-open-left {
- display: flex;
- height: 100%;
- animation-name: translateX;
- animation-timing-function: linear;
- animation-fill-mode: forwards;
- animation-duration: 300ms;
- }
- .uni-drawer-closed-left {
- display: flex;
- height: 100%;
- animation-name: translateXReverse;
- animation-timing-function: linear;
- animation-fill-mode: forwards;
- animation-duration: 600ms;
- }
- .uni-drawer-open-right {
- display: flex;
- height: 100%;
- flex-direction: row-reverse;
- animation-name: translateXRight;
- animation-timing-function: linear;
- animation-fill-mode: forwards;
- animation-duration: 300ms;
- }
- .uni-drawer-closed-right {
- display: flex;
- height: 100%;
- flex-direction: row-reverse;
- animation-name: translateXRightReverse;
- animation-timing-function: linear;
- animation-fill-mode: forwards;
- animation-duration: 600ms;
- }
- @keyframes translateX {
- from {
- transform: translateX(-110px);
- }
- to {
- transform: translateX(0px);
- }
- }
- @keyframes translateXReverse {
- from {
- transform: translateX(0px);
- }
- to {
- transform: translateX(-750px);
- }
- }
- @keyframes translateXRight {
- from {
- transform: translateX(300px);
- }
- to {
- transform: translateX(0px);
- }
- }
- @keyframes translateXRightReverse {
- from {
- transform: translateX(0px);
- }
- to {
- transform: translateX(750px);
- }
- }
- </style>
- <script>
- module.exports = {
- props: {
- /**
- * 显示模式(左、右),只在初始化生效
- */
- mode: {
- type: String,
- default: ''
- },
- /**
- * 蒙层显示状态
- */
- mask: {
- type: Boolean,
- default: true
- },
- /**
- * 遮罩是否可点击关闭
- */
- maskClick: {
- type: Boolean,
- default: true
- },
- /**
- * 抽屉宽度
- */
- width: {
- type: Number,
- default: 320
- }
- },
- data() {
- return {
- visibleSync: false,
- showDrawer: false,
- watchTimer: null,
- drawerWidth: 600,
- maskstyle: 'uni-mask-closed',
- unidrawerstyle: 'uni-drawer',
- flexdirection: 'row'
- }
- },
- onInit() {
- console.info("drawer oninit");
- this.$on('broaddrawerstate', this.drawerStateEvt);
- this.drawerWidth = this.width;
- if(this.mode=="left"){
- this.flexdirection="row";
- }else{
- this.flexdirection="row-reverse";
- }
- this.$watch('mode', 'onDrawerModeChange');
- },
- onDrawerModeChange: function (newValue, oldValue) {
- console.info("onDrawerModeChange newValue= " + newValue
- + ", oldValue=" + oldValue);
- if (newValue == 'left') {
- this.flexdirection = 'row';
- } else {
- this.flexdirection = 'row-reverse';
- }
- },
- drawerStateEvt(evt) {
- this.showDrawer = evt.detail.isOpen;
- console.info("drawerStateEvt this.showDrawer= " + this.showDrawer);
- if (this.showDrawer) {
- this.open();
- } else {
- this.close();
- }
- },
- close(type) {
- // 抽屉尚未完全关闭或遮罩禁止点击时不触发以下逻辑
- if ((type == 'mask' && !this.maskClick) || !this.visibleSync) {
- return;
- }
- console.info("close");
- this.maskstyle = 'uni-mask-closed';
- if (this.mode == "left") {
- this.unidrawerstyle = 'uni-drawer-closed-left';
- } else {
- this.unidrawerstyle = 'uni-drawer-closed-right';
- }
- this._change('showDrawer', 'visibleSync', false)
- },
- open() {
- // 处理重复点击展开的事件
- if (this.visibleSync) {
- return;
- }
- console.info("open this.mode="+this.mode);
- this.maskstyle = 'uni-mask-open';
- if (this.mode == "left") {
- this.unidrawerstyle = 'uni-drawer-open-left';
- } else {
- this.unidrawerstyle = 'uni-drawer-open-right';
- }
- this._change('visibleSync', 'showDrawer', true)
- },
- _change(param1, param2, status) {
- this[param1] = status;
- if (this.watchTimer) {
- clearTimeout(this.watchTimer);
- }
- this.watchTimer = setTimeout(() => {
- this[param2] = status;
- this.$emit('drawerchange', {'showDrawer':status});
- }, status ? 50 : 300)
- },
- dealDrawerSwipe: function(e) {
- console.info("dealDrawerSwipe");
- let direction=e.direction;
- if (this.mode == "left") {
- if(direction=="left"){
- this.close();
- }
- }else{
- if(direction=="right"){
- this.close();
- }
- }
- },
- }
- </script>
页面hello.ux代码:
- <import name="drawer" src="../Drawer/drawer.ux"></import>
- <template>
- <!-- Only one root node is allowed in template. -->
- <div class="container">
- <div class="title">
- <div class="icon" @click="isOpen">
- <text class="icon-item" for="[1,1,1,1]"></text>
- </div>
- <text class="page-title">drawer组件</text>
- </div>
- <stack style="width: 100%;height:100%;" ontouchstart="touchstart" ontouchend="touchend">
- <div class="content">
- <text style="color: #0faeff;">点击左上角按钮滑出左侧抽屉</text>
- <text class="txt" onclick="switchLocation">切换抽屉滑出位置左或右</text>
- <text style="color: #0faeff;margin-left: 10px;margin-right: 10px">手指在屏幕左侧边缘右滑亦可滑出左侧抽屉,手指在屏幕右侧边缘左滑亦可滑出右侧抽屉</text>
- <text style="color: #0faeff;margin-top: 20px;margin-left: 10px;margin-right: 10px">滑出抽屉的宽度默认为320px,如果输入的值超出600则按最大可设置宽度600px显示,小于300则按最小可设置宽度300px显示</text>
- <input id="input" class="input" type="number" placeholder="请输入宽度值,单位为px" value="{{inputValue}}" onchange="changeValue" />
- <text style="color: #0faeff;">键盘收起后,即可滑动或点击呼出抽屉</text>
- <text class="txt" onclick="maxWidth">设置抽屉为最大宽度</text>
- <text class="txt" onclick="minWidth">设置抽屉为最小宽度</text>
- </div>
- <drawer id="drawer" mode="{{drawerShowMode}}" width="{{drawerWidth}}" mask-click="true" @drawerchange="change">
- <tabs class="tabs" style="width: {{drawerWidth}}px;">
- <tab-content class="tabcontent">
- <list class="list">
- <block for="listarray">
- <list-item class="list-item" type="item" onclick="chooseItem($idx)">
- <text>第{{ $item }}章测试目录</text>
- </list-item>
- </block>
- </list>
- <text>this is second page</text>
- </tab-content>
- <tab-bar class="tabbar">
- <text class="text">part one</text>
- <text class="text">part two</text>
- </tab-bar>
- </tabs>
- </drawer>
- </stack>
- </div>
- </template>
-
- <style>
- .container {
- flex-direction: column;
- }
- /* 自定义内容属性 */
- .content {
- flex-direction: column;
- justify-content: center;
- align-items: center;
- width: 100%;
- height: 100%;
- }
- .txt {
- width: 80%;
- height: 80px;
- background-color: #0faeff;
- border-radius: 10px;
- text-align: center;
- margin-left: 80px;
- margin-top: 10px;
- margin-bottom: 10px;
- }
- .input {
- width: 80%;
- height: 80px;
- border: 1px solid #000000;
- margin-left: 80px;
- }
- /* 标题属性 */
- .title {
- height: 120px;
- width: 100%;
- align-items: center;
- background-color: #0faeff;
- padding-left: 20px;
- }
- .page-title {
- font-size: 40px;
- padding-left: 150px;
- }
- .icon {
- width: 60px;
- height: 60px;
- flex-direction: column;
- justify-content: space-around;
- }
- .icon-item {
- height: 4px;
- background-color: rgb(212, 212, 212);
- width: 100%;
- }
- .tabs {
- height: 100%;
- background-color: rgb(248, 230, 230);
- }
- .tabcontent {
- width: 100%;
- height: 90%;
- }
- .tabbar {
- width: 100%;
- height: 10%;
- }
- .text {
- width: 50%;
- height: 100%;
- font-size: 50px;
- text-align: center;
- }
- .list {
- flex: 1;
- width: 100%;
- }
- .list-item {
- height: 90px;
- width: 100%;
- padding: 0px 20px;
- border-bottom: 1px solid #f0f0f0;
- align-items: center;
- justify-content: space-between;
- }
- </style>
-
- <script>
- import prompt from '@system.prompt';
- module.exports = {
- data: {
- componentData: {},
- display: false,
- listarray: '',
- drawerWidth: 360,
- inputValue: '',
- drawerShowMode: 'right',
- movestartX: 0
- },
- onInit() {
- this.listarray = this.getList(20);
- },
- isOpen() {
- this.display = !this.display;
- if (this.display) {
- this.showDrawer();
- } else {
- this.closeDrawer();
- }
- },
- // 打开抽屉
- showDrawer(e) {
- this.$broadcast('broaddrawerstate', {
- isOpen: true
- })
- },
- // 关闭抽屉
- closeDrawer(e) {
- this.$broadcast('broaddrawerstate', {
- isOpen: false
- })
- },
- // 抽屉状态发生变化触发
- change(e) {
- console.info("change e=" + JSON.stringify(e));
- this.display = e.detail.showDrawer;
- },
- getList(num) {
- let list = []
- for (let i = 1; i <= num; i++) {
- list.push(i)
- }
- return list
- },
- switchLocation() {
- if (this.drawerShowMode == 'left') {
- this.drawerShowMode = 'right';
- } else {
- this.drawerShowMode = 'left';
- }
- },
- changeValue(e) {
- if (e.value >= 600) {
- this.drawerWidth = 600
- } else if (e.value <= 300) {
- this.drawerWidth = 300
- } else {
- this.drawerWidth = e.value
- }
- console.log("record", this.drawerWidth);
- if (e.value.length === 3) {
- this.$element('input').focus({ focus: false })
- }
- },
- maxWidth() {
- this.drawerWidth = 600
- },
- minWidth() {
- this.drawerWidth = 300
- },
- chooseItem(index) {
- prompt.showToast({
- message: `该内容为简单示例,点击了第${index + 1}条`,
- })
- },
- touchstart(e) {
- console.info("touchstart");
- this.movestartX = e.touches[0].offsetX;
- },
- touchend(e) {
- console.info("touch end e:" + JSON.stringify(e));
- let moveEndX = e.changedTouches[0].offsetX;
- if (this.drawerShowMode == "left") {
- //在屏幕左边缘从左往右边滑动时,呼出抽屉
- if (this.movestartX < 30) {
- let dis = moveEndX - this.movestartX;
- if (dis > 30) {
- this.showDrawer();
- }
- }
- } else {
- //在屏幕右边缘从右往左边滑动时,呼出抽屉
- if (this.movestartX > 720) {
- let dis = moveEndX - this.movestartX;
- if (dis < -30) {
- this.showDrawer();
- }
- }
- }
- },
- }
- </script>