抽屉组件

发布时间:2024-11-24 12:02

利用衣柜抽屉:内衣、袜子等小物件可放入抽屉进行收纳。 #生活技巧# #组织技巧# #衣柜整理#

简介

抽屉组件是一种特殊的弹出面板,可以模拟手机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}

抽屉展开、收起的回调事件。

实现抽屉展开和收起

抽屉默认是关闭不显示的,通过“display: none;”来隐藏。 通过X轴的平移动画控制展开、收起。

展开时,移到可视区域;收起时,移到屏幕之外。不管是展开还是收起,都是平滑的动画效果,代码如下。

左抽屉展开、收起动画:

@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); }} 通过div的flex-direction属性控制抽屉显示在左侧还是右侧。显示在左侧时,设置为“row”。显示在右侧时,设置为“row-reverse”。

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'); },

本文默认抽屉显示在左侧,左抽屉展开、收起的style代码如下:

.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;}

父子组件通信

父组件通过parentVm.$broadcast()向子组件传递抽屉展开、收起的事件,子组件通过$on()监听事件和参数。 子组件通过$watch()方法监听抽屉显示模式mode属性的变化,从而修改css样式,让其在正确的位置显示抽屉。 子组件通过drawerchange事件及参数通知父组件。

手势滑动呼出抽屉

通过监听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> <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>

网址:抽屉组件 https://www.yuejiaxmz.com/news/view/234285

相关内容

抽屉组件的制作方法
一种分隔件及抽屉组件.pdf
怎么组装抽屉?格子抽屉怎么组装? – 范的资源库
抽屉软件
【组合抽屉式收纳柜】组合抽屉式收纳柜价格
抽屉分割隔板自由组合抽屉收纳
组织垃圾抽屉
抽屉配件
帝爱优家抽屉组合分隔板抽屉隔板7CM宽 13.9元
抽屉分隔板内衣收纳格板自由组合抽屉收纳隔板 8.5元

随便看看