微信小程序 — 自定义picker选择器弹窗内容+textarea穿透bug
微信小程序中定义好的几种picker选择器,不管是日期选择器还是地区选择器,或是其他的都只有定死的样式和内容。
但是大多数开发程序的情况下还是需要自己写样式的,或是内容的。
例如:
代码如下:
<view class="free-btns" style="margin-top: 10vh;background:none;"> <button class="free-btn" bindtap="toggleDialog"> 选定国家:{{value}} </button> </view> <view class="free-dialog {{ showDialog ? 'free-dialog--show' : '' }}"> <view class="free-dialog__mask" bindtap="toggleDialog" /> <view class="free-dialog__container"> <view style="padding: 5% 5% 15%;"> <form bindsubmit='submit' bindreset="reset"> <view bindtap='freetoBack' class="free-button free-dialog-reset">取消</view> <view bindtap='freeBack' class="free-button free-dialog-submit">确定</view> <radio-group class='free-radios' bindchange="radioChange"> <label class="free-radio" bindtap="click" wx:for="{{items}}" wx:key="{{items}}" data-id="{{index}}" style="{{index==id?'background:#48c23d;color:#fff;':'background:#fff;color:#000;'}}"> <radio value="{{item.name}}" name="{{item.value}}"></radio> <label class="free-text">{{item.value}}</label> </label> </radio-group> </form> </view> </view> </view>
样式:
.free-dialog__mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 10; background: rgba(0, 0, 0, 0.7); display: none; } .free-dialog__container { position: fixed; left: 0; bottom: 0; width: 750rpx; background: white; transform: translateY(150%); transition: all 0.4s ease; z-index: 11; } .free-dialog--show .free-dialog__container { transform: translateY(0); } .free-dialog--show .free-dialog__mask { display: block; } /*模态框中的内容*/ .free-button{ display: inline-block; width:50px; text-align: center; font-size:20px; color:#707070; margin-bottom:20px; } .free-dialog-submit{ float: right; color:#48c23d; } radio-group{ margin:10rpx 0rpx; } radio-group>label{ width:22.5%; display: inline-block; border:1px solid #ddd; padding:10px 0px; margin:0px 2px 2px; } radio-group label radio{ width:100%; z-index: 3; display: none; } .checked{ background:#48c23d; color:#fff; } radio-group label .free-text{ width:100%; text-align: center; display: inline-block; }
js:
Page({ data: { showDialog: false, items: [ { name: '中国', value: '中国' }, { name: '美国', value: '美国' }, { name: '巴西', value: '巴西' }, { name: '日本', value: '日本' }, { name: '英国', value: '英国' }, { name: '法国', value: '法国' }, { name: '韩国', value: '韩国' }, { name: '俄罗斯', value: '俄罗斯' },] }, /*点击变色*/ click:function(e){ var id = e.currentTarget.dataset.id var that = this that.setData({ id:id }) }, onLoad: function (options) { var that = this that.setData({ value:'show' }) }, radioChange: function (e) { console.log('radio发生change事件,携带value值为:', e.detail.value) var that = this that.setData({ value: e.detail.value }) console.log(this.data.value) }, toggleDialog() { this.setData({ showDialog: !this.data.showDialog }); }, freeBack:function(){ var that = this if(this.data.value=='show'){ wx.showModal({ title: '提示', content: '你没有选择任何内容', }) } that.setData({ showDialog: !this.data.showDialog }) }, freetoBack: function () { var that = this wx.showModal({ title: '提示', content: '你没有选择任何内容', }) that.setData({ showDialog: !this.data.showDialog, value:'show', checked: false, }) }, })
遇到个BUG,就是表单中有textarea组件的时候,弹出层设置z-index,在模拟器上运行没问题,但是真机就无效了,textarea的内容就会穿透出来。
查了资料发现是官方的坑,textarea组件为原生组件层级最高,不能通过 z-index 控制层级。
解决办法:
1、隐藏 textarea:
设置一个值,用wx:if 去控制textarea的显示和隐藏。就是说当弹窗弹出的时候,在textarea做wx:if判断那个值为false,把textarea隐藏,然后关闭弹窗或点击其他的时候,把那个值再设为true,显示textarea。当然hidden原理一样。
代码如下:
<view class='itemArea'> <view class="form_item">留言类型:</view> <view class="form_item flex_row jc_between underLine"> <button class="free-btn" bindtap="toggleType"> {{typename}} </button> <view class="red" bindtap="toggleType">选择</view> </view> <view bindtap="hideView"> <view class="free-dialog {{ showType ? 'free-dialog-show' : 'free-dialog_mask' }}"> <view class="free-dialog_mask" bindtap="toggleType" /> <view class="free-dialog_container1"> <view class='p25'> <form bindsubmit='submit' bindreset="reset"> <radio-group class='free-radios' bindchange="radioChange2"> <label class="free-radio" bindtap="clickType" wx:for="{{msgtype}}" wx:key="{{msgtype}}" data-idn="{{index}}" data-name='{{item.typename}}' data-id='{{item.typeid}}' style="{{index==idn?'background:#FBDBDB;':'background:#F5F5F5;'}}"> <radio value="{{item.typename}}" name="{{item.typename}}"></radio> <label class="free-text">{{item.typename}}</label> </label> </radio-group> </form> </view> </view> </view> </view> </view> <view class='pb20'> <view class="form_item">留言内容:</view> <view class="form_item"> <textarea class='form_textarea' hidden="{{noTextarea}}" placeholder='请填写留言内容' bindinput='msgcontent' value="{{msgcontent}}" name="msgcontent"></textarea> </view> </view>
js:
data: { noTextarea: false, //textarea弹出层的时候隐藏 }, //点击其他位置隐藏遮罩层 hideView:function(){ var that = this; that.setData({ noTextarea: false, }) }, //选择留言类型 toggleType: function () { this.setData({ showType: !this.data.showType, noTextarea: true, }); }, radioChange2: function (e) { var that = this; that.setData({ typename: e.detail.value, showType: !this.data.showType, noTextarea: false, }) console.log(this.data.typename); }, clickType:function(e){ var that = this; var idn = e.currentTarget.dataset.idn; var id = e.currentTarget.dataset.id; that.setData({ idn: idn, typeid: id, noTextarea: false, }) console.log(that.data.typeid); },
2、还有一种解决方法是使用替代元素
有时候, textarea
穿透的不是遮罩层,或者遮罩层以一种半透明而非完全遮住页面内容的形式呈现,担心用户能够看到因为 textarea
的消失而导致页面跳动,产生不好的用户体验,那么就可以使用替代元素来替代 textarea
而非将之直接隐藏掉。
基本的 textarea
组件只接受文本的输入,抛开可输入性的话,外观上看就是一个含有文本节点的简单元素,只需要获取当前状态下的 textarea
中输入的文字,将之赋予给一个样式与 textarea
相同的普通元素,就达到了临时替代的效果。
<!-- 这是真正的 textarea组件 --> <textarea id="text-area" value="{{txtRealContent}}" bindinput='txtInput' wx:if="{{!showMask}}" /> <!-- 这是用于模拟 textarea的替代元素 --> <view class='rich-text' style="{{('height:' + txtHeight + 'px')}}" wx:else> <rich-text nodes="{{txtRealContent}}"></rich-text> </view>
- 由于需要实时获取
textarea
中已经输入的内容,所以给textarea
元素加了个bindinput
的监听器 showMask
用于标识是否显示遮罩层(或者其他可能会被 textarea穿透的浮动元素),如果显示遮罩层,则隐藏textarea
元素,并显示替代原宿- 这里
textarea
的隐藏使用了wx:if
,会使其彻底地从页面中消失,而重新显示出来的时候,textarea
元素会重新创建,丢失原先输入,所以给其加了个value
属性,其值txtRealContent
就是缓存的textarea
已经输入的文本内容;如果你不用这种方法,不让textarea
完全显示,而仅仅是隐藏,例如使用hidden="{{ showMask ? true :false }}"
,因为不涉及到textarea
的删除与重建,所以就无需添加value
属性来控制文本内容了。 textarea
是可以输入可换行的文本内容的,所以这里使用了rich-text
组件,在使用的时候,我发现rich-text
好像不支持溢出隐藏,所以又额外在其外面包了一层view
组件,并将其高度设置为和textarea
相同。
上面四个步骤,都比较简单,稍微需要注意的是,如果 textarea
的内容包含了换行文本,则需要对换行符进行处理:
textareaContent.replace(/\n/g, '<br/>')
如果你想让
textarea
自动增加高度而不是固定高度,给 textarea
加了个 auto-height
,那么就需要“实时”获取其高度 说是 “实时”,其实也并不是那么实时,不考虑其他样式的变化, textarea
的高度与行数有关,每增减一行,其高度才会变化,所以只需要监控其内容行数的变化即可,恰好 textarea
组件也已经提供了这个监控器:bindlinechange
。实例代码如下:
<view class="page-body"> <button bindtap="changeMaskVisible">切换mask</button> <view class="textarea-wrp"> <textarea id="text-area" value="{{txtContent}}" bindinput='txtInput' bindlinechange="textAreaLineChange" wx:if="{{!showMask}}" auto-height /> <view class='rich-text' style="{{('height:' + txtHeight + 'px')}}" wx:else> <rich-text nodes="{{txtRealContent}}"></rich-text> </view> </view> <button>Footer</button> <view wx:if="{{showMask}}" bindtap="changeMaskVisible" class="mask"> <view class="mask-content"></view> </view> </view>
样式:
.rich-text { overflow: hidden; } .mask { position: fixed; top: 0; right: 0; bottom: 0; left: 0; background-color: rgba(0, 0, 0, .6); z-index: 10; } .mask-content { position: fixed; top: 44%; left: 50%; height: 60%; width: 60%; transform: translate(-50%, -50%); background-color: yellowgreen; z-index: 12; }
js:
Page({ data: { txtRealContent: '', txtContent: '', showMask: false, txtHeight: 0 }, textAreaLineChange(e) { this.setData({ txtHeight: e.detail.height }) }, txtInput(e) { this.setData({ txtContent: e.detail.value }) }, changeMaskVisible(e) { if (!this.data.showMask) { // 将换行符转换为wxml可识别的换行元素 <br/> const txtRealContent = this.data.txtContent.replace(/\n/g, '<br/>') this.setData({ txtRealContent }) } this.setData({ showMask: !this.data.showMask }) } })