scroll-view 组件是为了特定的滚动场景设计的。与 movable-view、movable-area、cover-view 等组件一样都是为了开发者实现特定场景下的业务功能而设计开发的。
scroll-view 的滚动属性主要实现了两套功能:左右或上下滚动、下拉更新。
与滚动有关的属性有以下几个:
white-space: nowrap; display: inline-block;
。如果同时开启横向、纵向两个方向的滚动,当通过 scroll-into-view 滚动时,滚动行为变化有两种:如果不加 scroll-with-animation 属性,也就是不开启动画,可以同时在 x、y 两个方向上瞬时移动到目标位置;如果开启动画,同一时间只能在一个方向上滚动,有时在 x 轴滚动,有时在 y 轴滚动。所以 scroll-x 和 scroll-y 最好不要同时开启。示例代码如下:
index.wxml:
<view class="page-section">
<view class="page-section-spacing">
<scroll-view enable-flex scroll-into-view="{{scrollIntoViewId}}" bindscroll="onScroll" scroll-y scroll-x scroll-with-animation="{{false}}" style="width: 100%;height:300rpx;">
<view id="childview{{item}}0" wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9,10]}}" class="scroll-row">{{item}}
<view wx:for="{{[0, 1, 2, 3, 4, 5,6,7,8,9]}}" class="scroll-item" id="childview{{item}}{{item2}}" wx:for-item="item2">{{item}}:{{item2}}</view>
</view>
</scroll-view>
</view>
<view class="btn-area">
<button type="primary" bindtap="scrollToView1">滚动到子组件2</button>
</view>
</view>
index.js:
let viewId = 5
Page({
scrollToView1(){
viewId += 2
this.setData({
scrollIntoViewId:'childview'+viewId
})
console.log(this.data.scrollIntoViewId)
},onScroll(e){
console.log(e.detail.scrollTop, e.detail.scrollLeft, e.detail.scrollHeight,e.detail.scrollWidth)
},
})
下拉更新的属性主要有以下几个:
最佳实践为以下步骤:
overflow-anchor:auto
的样式,用于解决 Android 机型不兼容的问题。white-space: nowrap; display: inline-block;
display: flex;
样式,如果是我们自己添加,是加在了外围的容器上,只有通过这个属性才能加到内部真正的容器上。微信在 WeUI 扩展组件库中给出了一个长列表组件:recycle-view,用于渲染无限长的列表。
通过监听 scroll 事件,只渲染当前视图窗口内的 list 列表,看不见的地方用空白的占位符代替。
在使用 recycle-view 扩展组件的时候,batch 属性的值必须为 batchSetRecycleData。
在 JavaScript 代码中,调用 createRecycleContext 时传入的 dataKey 是 recycleList。这个名称必须与 WXML 中的 wx:for 指定的数据名称一致。如果一个页面中还使用了另外一个长列表,则需要再换一个名字。
使用方法如下:
1、安装组件。安装完后在菜单栏的工具里,点击『构建 npm』。
npm install --save miniprogram-recycle-view
2、在页面的 json 配置文件中添加 recycle-view 和 recycle-item 自定义组件的配置。
{
"usingComponents": {
"recycle-view": "miniprogram-recycle-view/recycle-view",
"recycle-item": "miniprogram-recycle-view/recycle-item"
}
}
3、index.wxml:
<view class="page-section">
<recycle-view height="200" batch="{{batchSetRecycleData}}" id="recycleId" batch-key="batchSetRecycleData" style="background:white;">
<recycle-item wx:for="{{recycleList}}" wx:key="index" class='item'>
<view>
{{item.id}}: {{item.name}}
</view>
</recycle-item>
</recycle-view>
</view>
4、index.js:
const createRecycleContext = require('miniprogram-recycle-view')
function rpx2px(rpx) {
return (rpx / 750) * wx.getSystemInfoSync().windowWidth
}
Page({
onReady: function () {
var ctx = createRecycleContext({
id: 'recycleId',
dataKey: 'recycleList',
page: this,
itemSize: {
width: rpx2px(650),
height: rpx2px(100)
}
})
let newList = []
for (let i = 0; i < 20; i++) {
newList.push({
id: i,
name: `标题${i + 1}`
})
}
ctx.append(newList)
const arr = []
for (let i = 0; i < 20; i++) arr.push(i)
this.setData({
arr
})
setTimeout(() => {
this.setData({
triggered: true,
})
}, 1000)
//
let activeTab = 0, page=1, res = {something:''}
let tabsData = this.data.tabs[activeTab] || {list:[]}
tabsData.page = page+1
tabsData.list.push(res)
let key = `tabs[${activeTab}]`
this.setData({
[key]: tabsData
})
console.log(this.data.tabs)
},
})
主要实现两个功能:1、单击左侧菜单,右侧区域自动滚动到相应的位置;2、在右侧滚动的时候,左侧菜单自动同步选择并高亮显示。
第一个功能点通过 scroll-into-view 属性去实现。
<!-- 左侧菜单 -->
<scroll-view scroll-y class="nav">
<view wx:for='{{list}}' wx:key='{{item.id}}' id='{{item.id}}' class='navList{{currentIndex==index?"active":""}}'
bindtap="menuListOnClick" data-index='{{index}}'>{{item.name}}</view>
</scroll-view>
<!-- 右侧菜单 -->
<scroll-view scroll-y scroll-into-view='{{activeViewId}}' bindscroll='scrollFunc'>
<view class="fishList" wx:for='{{content}}' id='{{item.id}}' wx:key='{{item.id}}'>
<p>{{item.name}}</p>
</view>
</scroll-view>
scroll-into-view 绑定了一个 activeViewId 变量,需要与左侧菜单中的 id 对应起来。在点击左侧每一个具体菜单的时候,bindtap 绑定的 JavaScript 函数中需要将 activeViewId 指定为当前点击的这个 item.id。
// 点击左侧菜单
menuListOnClick:function(e){
let me= this;
me.setData({
activeViewId: e.target.id,
currentIndex: e.target.dataset.index
})
}
e 是事件对象,取到 target.id,赋值给 activeViewId,设置完后功能就实现了。
第二个功能点是在右侧滚动的时候,左侧菜单同时自动去选择并带高亮。
在右侧滚动的时候,将 bind 的 scroll 事件绑定到 scrollFunc 的 JavaScript 函数上面。
// 滚动时触发,计算当前滚动到的位置对应的菜单是哪个
scrollFunc:function(e){
this.setData({
scrollTop: e.detail.scrollTop
})
for(let i= 0; i< this.data.heightList.height; i++){
let height1= this.data.heightList[i];
let height2= this.data.heightList[i+ 1];
if(!height2|| (e.detail.scrollTop>= height1 && e.detail.scrollTop < height2)){
this.setData({
currentIndex: 1
})
return;
}
}
this.setData({
currentIndex: 0
})
}
WeUI 组件库中有一个 vtabs 组件,是一个有侧边栏分类的商品浏览组件。
vtabs 是一个选项卡组件,在使用这个组件的时候,要把它和 vtabs-content 结合起来进行实现。其中 vtabs-content 是 vtabs 的一个子组件。
1、安装组件。
npm i @miniprogram-component-plus/vtabs --save
npm i @miniprogram-component-plus/vtabs-content --save
安装完后在菜单栏的工具里,构建 npm。
2、在页面的 json 配置文件中添加 recycle-view 和 recycle-item 自定义组件的配置。
{
"usingComponents": {
"mp-vtabs": "@miniprogram-component-plus/vtabs/index",
"mp-vtabs-content": "@miniprogram-component-plus/vtabs/index"
}
}
3、index.wxml:
<mp-vtabs
vtabs="{{vtabs}}"
activeTab="{{activeTab}}"
bindtabclick="onTabCLick"
bindchange="onChange"
class="test"
>
<block wx:for="{{vtabs}}" wx:key="title" >
<mp-vtabs-content tabIndex="{{index}}">
<view class="vtabs-content-item">我是第{{index + 1}}项: {{item.title}}</view>
</mp-vtabs-content>
</block>
</mp-vtabs>
4、index.js:
Page({
data: {
vtabs: [],
activeTab: 0,
},
onLoad() {
const titles = ['热搜推荐', '手机数码', '家用电器',
'生鲜果蔬', '酒水饮料', '生活美食',
'美妆护肤', '个护清洁', '女装内衣',
'男装内衣', '鞋靴箱包', '运动户外',
'生活充值', '母婴童装', '玩具乐器',
'家居建材', '计生情趣', '医药保健',
'时尚钟表', '珠宝饰品', '礼品鲜花',
'图书音像', '房产', '电脑办公']
const vtabs = titles.map(item => ({title: item}))
this.setData({vtabs})
},
onTabCLick(e) {
const index = e.detail.index
console.log('tabClick', index)
},
onChange(e) {
const index = e.detail.index
console.log('change', index)
}
})
5、index.wxss:
.vtabs-content-item {
width: 100%;
height: 300px;
box-sizing: border-box;
border-bottom: 1px solid #ccc;
padding-bottom: 20px;
}
picker 本身有一个模式是 region,是省市区三级联动的。这个默认组件在某些特定场景下,不能满足我们的样式需求。另外不一定是省市区,还有像基于其它数据源的选择器,也可以自定义实现,样式也可以自如控制。
index.wxml:
<view class="section">
<view class="section__title">省市区选择器</view>
<picker mode="region" bindchange="bindRegionChange" value="{{region}}" custom-item="{{customItem}}">
<view class="picker">
当前选择:{{region[0]}},{{region[1]}},{{region[2]}}
</view>
</picker>
</view>
pick-view 基于子组件 picker-view-column 这种松耦合的架构实现的,本身没有数据源。它的所有数据都是在 picker-view-column 这个子组件里面,由开发者通过 wx:for 循环去绑定。
picker 是底部滑出的,picker-view 是页面嵌入的,为了实现底部滑出效果,可以把 picker-view 放在滑出的面板上。至于嵌入组件的蒙层的样式效果,可以通过 mask-style 或者 mask-class 控制,这两个属性是专门用于控制蒙层效果的。
region-picker-view 目前有两个问题。
一是最好不要在它的 change 事件里面去改变视图,因为在滑动的时候可能会涉及到连续的多次的滑动,在用户还没有选到目标值之前,可能会涉及到多次的 change 事件派发。最好是在用户选择结束之后,例如在 touchend 事件中,再去判断有没有变化。如果有变化,再去改变数据源。
二是通过测试发现关于 picker 组件当 mode 为 multiSelector 时,当手指滑动时,它的 columnchange 事件会有多次派发。picker 组件的 change 事件是在单击『确定』按钮之后才派发的,而对于 picker-view 组件,在滑动选择的过程中,change 事件是不派发的,change 事件只在选定之后派发了一次。在 picker-view 组件中,还有 pickerstart 和 pickend 事件,分别代表滚动选择的开始和结束,这就没必要从 touchend 事件中自己做状态的判断了。可以从 change 事件中先拿到 value,然后在 pickend 事件里,在选择结束的时候再去作这个逻辑的处理。
从以上两点看,picker-view 组件的涉及是优于 picker 组件的,picker 组件的功能,使用 picker-view 也是可以完全实现的;关于交互的操作,最好是写在 WXS 模块里面,而不是在 JavaScript 里面。减少视图层与逻辑层之间的通讯,可以显著提高界面的流畅性。
region-picker-view2 就是使用 WXS 脚本,将 region-picker-view 自定义组件改写。
index.wxml:
<!-- 自定义选择器 -->
<view class="page__section">
<view class="page__section-title">两个自定义实现选择器</view>
<!-- js滚动选择器 -->
<region-picker-view bindchange="onRegionChange"></region-picker-view>
<!-- wxs滚动选择器 -->
<region-picker-view2 bindchange="onRegionChange"></region-picker-view2>
</view>
index.json:
{
"usingComponents": {
"region-picker-view": "/components/region-picker-view/index",
"region-picker-view2": "/components/region-picker-view2/index"
}
}
一个 ComponentDescriptor 组件描述对象有以下几个方法:
#
开头的组件 id,也可以是以 .
开头的样式类名称。index.wxml:
<view class="section section_gap">
<text class="section__title">设置最小 / 最大值,步进为 5</text>
<view class="body-view">
<slider bindchange="slider4change" bindchanging="onSliderChanging" min="50" max="200" show-value step="5" />
</view>
</view>
index.js
var pageData = {}
for (var i = 1; i < 5; ++i) {
(function (index) {
pageData[`slider${index}change`] = function (e) {
console.log(`slider${index}发生change事件,携带值为`, e.detail.value)
}
})(i);
}
Page(Object.assign({
data:{},
onSliderChanging(e){
console.log(e.type, e.detail.value);
},
}, pageData))
自定义纵向 slider 采用 WXS 脚本编写。
竖向 slider 是以底部为起点的,滑动到底部为 min 值,滑动到顶部为 max 值,整个 slider 分成上、中、下三部分:灰色的竖条、白色的滑块、绿色的竖条。中间滑块 slider-middle 是不占用大小的,宽高都是 0,它的子组件 slider-block 是有大小的,并且它的 postion 样式是 relative。它是以相对定位的方式挂在中间位置的,也就是在 slider-middle 里。
index.wxml:
<view class="section section_gap">
<text class="section__title">自定义竖向slider</text>
<view class="body-view">
<view style="height: 400rpx;margin: 20px;display: flex;justify-content: space-around">
<slider-vertical block-color="#ffffff" block-size="28" backgroundColor="#e9e9e9" activeColor="#1aad19" bindchange="slider1change" bindchanging="slider1changing" step="1" min="50" max="200" value="0" disabled="{{false}}" show-value="{{true}}"></slider-vertical>
<slider-vertical block-color="#ffffff" block-size="28" backgroundColor="#e9e9e9" activeColor="#1aad19" bindchange="slider1change" bindchanging="slider1changing" step="5" min="50" max="200" value="115" disabled="{{false}}" show-value="{{false}}"></slider-vertical>
</view>
</view>
</view>
index.js:
var pageData = {}
for (var i = 1; i < 5; ++i) {
(function (index) {
pageData[`slider${index}change`] = function (e) {
console.log(`slider${index}发生change事件,携带值为`, e.detail.value)
}
})(i);
}
Page(Object.assign({
data:{},
slider1change: function (e) {
console.log("change:",e)
},
slider1changing: function (e) {
console.log("changing:",e)
}
}, pageData))
index.json:
{
"usingComponents": {
"slider-vertical": "/components/vertical-slider/index"
},
"navigationStyle": "custom",
"navigationBarTitleText": "自定义导航标题"
}
小程序的导航组件有两个:functional-page-navigator 和 navigator。
前者是在插件中使用的,仅能跳转到插件的功能页。后者是小程序标准的导航组件,可以通过设置不同的跳转方式,实现不同的跳转功能。
open-type 一共有 6 个合法值:
在小程序页面的 json 配置文件中,设置 navigationStyle 为 custom,开启页面导航栏的自定义。在开启以后,系统状态栏也会透明。
index.wxml:
<navigation-bar
ext-class="page-navigator-bar"
active="{{active}}"
loading="{{loading}}">
<view class="left" slot="left">
<icon bindtap="goBack" class="iconfont icon-back"></icon>
<icon bindtap="goHome" class="iconfont icon-home"></icon>
</view>
<view slot="center">
<view>自定义导航标题</view>
</view>
</navigation-bar>
<view style="width:100%;height:400px;"></view>
<image class="top-banner" src="https://qiniu-image.qtshe.com/1557133211411_684.jpg" mode="widthFix" />
<view class="operate-wraper" style="background-color:#f2f2f2;--topBarHeight:{{topBarHeight}}px;">
</view>
index.js:
Page({
data: {
loading: false,
active: true
},
//点击back事件处理
goBack: function () {
wx.navigateBack();
this.triggerEvent('back');
},
//返回首页
goHome:function(){
wx.reLaunch({
url: '/pages/index/index'
})
},
onPageScroll(res) {
console.log(res);
if (res.scrollTop > 400) {
if (!this.data.active) {
this.setData({
active: true
})
}
} else {
if (this.data.active) {
this.setData({
active: false
})
}
}
}
})
index.wxss:
@font-face {
font-family: 'iconfont'; /* Project id 2503355 */
src: url('//at.alicdn.com/t/font_2503355_3o3ks3b2xfn.woff2?t=1620132700001') format('woff2'),
url('//at.alicdn.com/t/font_2503355_3o3ks3b2xfn.woff?t=1620132700001') format('woff'),
url('//at.alicdn.com/t/font_2503355_3o3ks3b2xfn.ttf?t=1620132700001') format('truetype');
}
.iconfont{
font-family: 'iconfont';
}
.icon-back::after{
content: '\e67c';
font-size: 22px;
}
.icon-home::after{
content: '\e64e';
font-size: 22px;
}
.left icon:last-child{
padding-left: 20rpx;
}
.page-navigator-bar .navigator-normal .icon-back{
color: white;
}
.page-navigator-bar .navigator-normal .icon-home{
color: white;
}
.page-navigator-bar .navigator-active .icon-back{
color: black;
}
.page-navigator-bar .navigator-active .icon-home{
color: black;
}
index.json:
{
"usingComponents": {
"navigation-bar":"/components/navigation-bar/index"
},
"navigationStyle": "custom"
}
返回按钮和主页按钮是在外面定义的,它们的切换是通过 ext-class 扩展样式属性实现的。设置这个属性,是为了在外面可以控制内部组件的样式。只要自定义组件使用插槽,基本上这个机制就是需要的。
image 组件是最常使用的媒体组件之一,这个组件本身有一个 lazy-load 属性,该属性已经实现了图片的懒加载功能。下面是一些 image 组件的技术问题:
Webp 介绍:是 image 组件的布尔属性,开启这个属性,代表 url 可以设置 Webp 这种格式的图片。Webp 是一种同时提供了有损压缩与无损压缩,并且是可逆的图片压缩的这种文件格式。image 组件默认不解析这种图片格式。
Webp 优势:它具有更优的图像数据压缩算法,能带来更小的图片体积,并且拥有肉眼识别无差异的图像质量。同时提供了无损及有损的两种图片压缩模式,还提供 alpha 透明,以及动画的特性,对 JPEG 和 PNG 等这些图片格式的转化都有支持。Webp 既可以替代 JPEG、PNG 这些静态的图片,也可以替代 GIF 这种动态的图片。
show-menu-by-longpress 属性:是图片组件的一个布尔属性,开启这个属性,就是开启了长按图片,显示识别小程序码的一个菜单。这种识别仅是在 wx.previewImage 接口调用,开始预览图片的时候,才可以识别。
mina-lazy-image 主要实现原理是使用 wx.createIntersectionObserver 接口,创建 IntersectionObserver 实例,用这个实例去判断图片是否出现在用户的视图窗口中。如果出现了,再进行加载。
IntersectionObserver 主要用于推断某些组件节点是否可以被用户看见、有多大比例可以被用户看见。一共有下面四个方法:
npm install --save mina-lazy-image
index.wxml:
<view class="page-head">
<text class="page-head__title">slider</text>
<text class="page-head__desc">滑块</text>
</view>
<view class="page-section">
<text class="page-section__title">use image</text>
<scroll-view class="cardbox">
<button wx:if="{{item.live.play_urls}}" class="card" hover-class='none' wx:for="{{content}}" wx:key="*this" bindtap="gotoLive" data-url="{{item.live.play_urls.hdl.ORIGIN}}" data-ava="{{item.live.user_info.avatar}}" data-name="{{item.live.user_info.name}}" data-audience="{{item.live.audience_num}}" data-lid="{{item.live.id}}" data-cacheprepic="{{item.live.pic}}" data-prepic="{{item.live.pic_320}}" data-share_desc="{{item.live.share_info.wechat_contact.cn.text}}" style="position: relative;">
<view class="image_card">
<image class="showpic" mode="aspectFill" src="{{item.live.pic_320}}" lazy-load="{{true}}" />
<view class="cover" />
<text class="audience">{{item.live.audience_num}}观众</text>
</view>
<view class="user_card" catchtap="gotoHome" data-uid="{{item.live.user_info.id}}">
<view class="avabox">
<image src="{{item.live.user_info.avatar}}" lazy-load="{{true}}" class="ava" data-uid="{{item.live.user_info.id}}" />
<image class="vip" wx:if="{{item.live.vip}}" lazy-load="{{true}}" src="http://img08.oneniceapp.com/upload/resource/9e7ca7ece11143b49fc952cfb2520e43.png" />
</view>
<text class="user_name">{{item.live.user_info.name}}</text>
</view>
</button>
<button wx:if="{{item.live.playback_urls}}" class="card" open-type='getUserInfo' bindtap="gotoPlayback" wx:for="{{content}}" data-url="{{item.live.playback_urls.hls.ORIGIN}}" wx:key="*this" >
<view class="image_card">
<image className="showpic" mode="aspectFill" src="{{item.live.pic_320}}" lazy-load="{{true}}" />
<view class="cover" />
<text class="audience">{{item.live.audience_num}}观众</text>
<image class="back" lazy-load="{{true}}" src="http://img08.oneniceapp.com/upload/resource/002bdceaa732f300e33ab8b2cb84dd17.png" />
</view>
<view class="user_card">
<view class="avabox">
<image src="{{item.live.user_info.avatar}}" class="ava" lazy-load="{{true}}" />
<image class="vip" wx:if="{{item.live.vip}}" lazy-load="{{true}}" src="http://img08.oneniceapp.com/upload/resource/9e7ca7ece11143b49fc952cfb2520e43.png" />
</view>
<text class="user_name">{{item.live.user_info.name}}</text>
</view>
</button>
</scroll-view>
</view>
<view class="page-section">
<text class="page-section__title">use mina-lazy-image</text>
<scroll-view class="cardbox">
<button wx:if="{{item.live.play_urls}}" class="card" hover-class='none' wx:for="{{content}}" wx:key="*this" bindtap="gotoLive" data-url="{{item.live.play_urls.hdl.ORIGIN}}" data-ava="{{item.live.user_info.avatar}}" data-name="{{item.live.user_info.name}}" data-audience="{{item.live.audience_num}}" data-lid="{{item.live.id}}" data-cacheprepic="{{item.live.pic}}" data-prepic="{{item.live.pic_320}}" data-share_desc="{{item.live.share_info.wechat_contact.cn.text}}" style="position: relative;">
<view class="image_card">
<mina-lazy-image mode="aspectFill" src="{{item.live.pic_320}}" />
<view class="cover" />
<text class="audience">{{item.live.audience_num}}观众</text>
</view>
<view class="user_card" catchtap="gotoHome" data-uid="{{item.live.user_info.id}}">
<view class="avabox">
<mina-lazy-image src="{{item.live.user_info.avatar}}" data-uid="{{item.live.user_info.id}}" />
<image class="vip" wx:if="{{item.live.vip}}" lazy-load="{{true}}" src="http://img08.oneniceapp.com/upload/resource/9e7ca7ece11143b49fc952cfb2520e43.png" />
</view>
<text class="user_name">{{item.live.user_info.name}}</text>
</view>
</button>
<button
wx:if="{{item.live.playback_urls}}"
class="card"
open-type='getUserInfo'
bindtap="gotoPlayback"
wx:for="{{content}}"
data-url="{{item.live.playback_urls.hls.ORIGIN}}"
wx:key="*this"
>
<view class="image_card">
<mina-lazy-image mode="aspectFill" src="{{item.live.pic_320}}" />
<view class="cover" />
<text class="audience">{{item.live.audience_num}}观众</text>
<image class="back" lazy-load="{{true}}" src="http://img08.oneniceapp.com/upload/resource/002bdceaa732f300e33ab8b2cb84dd17.png" />
</view>
<view class="user_card">
<view class="avabox">
<mina-lazy-image src="{{item.live.user_info.avatar}}" />
<image class="vip" wx:if="{{item.live.vip}}" lazy-load="{{true}}" src="http://img08.oneniceapp.com/upload/resource/9e7ca7ece11143b49fc952cfb2520e43.png" />
</view>
<text class="user_name">{{item.live.user_info.name}}</text>
</view>
</button>
</scroll-view>
</view>
<view class="page-section">
<text class="page-section__title">设置step</text>
<image bindtap="previewImage" data-url="http://t.cn/A622upBw" show-menu-by-longpress src="http://t.cn/A622upBw" mode="widthFit"></image>
</view>
index.js:
const app = getApp()
Page({
/**
* 页面的初始数据
*/
data: {
},
onLoad: function () {
wx.request({
url: 'https://wxapi.kkgoo.cn/live/discover?type=hot',
method:'POST',
success:(res) => {
this.setDis(res);
}
})
},
setDis(r) {
let newData = r.data.data;
this.data.nextKey = newData.nextkey ? newData.nextkey : this.data.nextKey;
this.setData({
content: newData.discover ? newData.discover : this.data.content,
banneritem: newData.cards ? newData.cards.slice(0, newData.cards.length - 1) : this.data.banneritem
})
},
previewImage(e){
console.log(e);
let url = e.currentTarget.dataset.url
wx.previewImage({
current:url,
urls: [url],
})
}
})
index.wxss:
/* miniprogram/pages/2.14/index.wxss */
.lazy-image{
}
/* 用户列表相关样式 */
.main{
font-size:0;
width:100%;
height: 100%;
font-family: 'PingFangSC-Semibold';
}
.title{
text-align:center;
font-size: 0;
}
.u_title{
display: inline-block;
width:100%;
font-size: 24rpx;
line-height: 24rpx;
margin:20rpx 0;
font-weight: bold;
}
.d_title{
display: inline-block;
width:100%;
line-height: 22rpx;
font-size: 22rpx;
}
.cardbox{
width: 100%;
font-size: 0;
box-sizing: border-box;
padding: 0 32rpx;
/*margin-top:60rpx;*/
display: inline-block;
}
button::after{
border: none
}
button{
width: auto !important;
padding-left: 0 !important;
padding-right: 0 !important;
background-color: #fff;
}
.card{
display: inline-block;
float:left;
/* margin-top:60rpx; */
}
.card .image_card{
width: 268rpx;
height: 268rpx;
border-radius: 8rpx;
position: relative;
}
.cover{
position: absolute;
/* width: 327rpx;
height: 327rpx; */
top: 0;
left: 0;
background-color: rgba(0,0,0,0.3);
z-index: 99;
border-radius: 8rpx;
}
.card .image_card .audience{
font-size: 22rpx;
color:#fff;
position: absolute;
left:16rpx;
top:16rpx;
z-index:999;
font-weight: bold;
}
.card .image_card .back{
position: absolute;
right:16rpx;
top:16rpx;
width: 56rpx;
height: 32rpx;
z-index: 999;
}
.card .user_card{
margin-top: 20rpx;
margin-bottom: 20rpx;
float:left;
margin-right: 15rpx;
}
.card .user_card .avabox{
width:48rpx;
height: 48rpx;
margin-right: 15rpx;
position: relative;
display: inline-block;
vertical-align: middle;
}
.card .user_card .avabox .ava{
width: 100%;
height: 100%;
border-radius: 8rpx;
vertical-align: top
}
.card .user_card .avabox .vip{
position: absolute;
width: 32rpx;
height: 32rpx;
bottom:-5rpx;
right:-5rpx;
border-radius: 50%;
background: red;
}
.card .user_name {
width: auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
font-size: 24rpx;
text-align: start;
display: inline-block;
vertical-align: middle;
font-weight: bold;
}
.card:nth-child(odd){
margin-right:32rpx;
}
.showpic{
width: 100%;
height: 100%;
border-radius: 8rpx;
overflow: hidden;
}
.scroll-end{
float: left;
height: 50rpx;
width: 100%;
color: #999;
line-height: 50rpx;
font-size: 28rpx;
text-align: center;
}
index.json:
{
"usingComponents": {
"mina-lazy-image": "mina-lazy-image/index"
}
}
image 组件拉取图片的本质是使用 wx.downloadFile 接口加载图片的资源,当加载以后,把加载的图像再绘制出来。很多时候由于图片的格式不规范,例如线上的 SSL 证书有问题,或者是文件的描述信息,例如 content-type、length 等信息不标准不完整,还有可能是由于这个服务器发生了 302 跳转等等原因导致图片拉取不成功,看到的现象就是这个图片没有显示出来。有时候网络不好,加载超时,图片也不会显示。
对于网络不好的情况,可以用 image 组件的 binderror 事件属性处理,监听 error 事件。当监听到错误以后,重新给 src 属性赋值,一般通过这种方法可以解决。
因为机型不同,尺寸也不一样。image 组件有一个关于缩放的属性 mode,经常使用的值有三个:scaleToFill、aspectFit、aspectFill。
scaleToFill:不保持纵横比例缩放图片,使图片的宽高完全拉伸填充整个 image 元素。
aspectFit:保持纵横比例去缩放图片,使图片的长边可以完全显示出来,可以完整的将图片显示出来,不会对图片有任何的裁剪。
aspectFill:保持纵横比例缩放图片,并且只保证图片的短边可以完全显示出来。图片同时只能在一个方向水平或垂直方向是完整的,在另外一个方向上将会发生裁剪。
由于 image 在加载图片的时候会有一些缺陷,所以实现背景图片适配所有机型这个问题,最好不要使用 mode 属性去实现,最好使用 WXSS 样式来实现。
.container{
position: fixed;
width: 100%;
height: 100%;
background-color: azure;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: -1;
}
.container::after{
content: "";
background: url(https://caroly.site/caroly_img/86009625_p0_1607479719466.png) no-repeat center center;
background-size: cover;
opacity: 0.5;
top: 0;
bottom: 0;
left: 0;
right: 0;
position: absolute;
}
要用一个尽量覆盖所有屏幕比例的大小来做这个背景图片。一般情况下是新建一个 750*1334 这样一个大小的背景图片,并且把分辨率设置为 72,用这样的尺寸做背景图片。
通过 image-cropper 组件进行裁剪,它可以让图片通过拖拽的方式,选择范围可以任意裁剪。
实现原理:通过四角的一个控制点去控制选择的范围,四角的控制点是通过 view 渲染出来的,图片加载完成以后绘制到 canvas 画布上,选定裁剪范围,再通过 wx.canvasToTempFilePath 接口生成一个临时的图片,临时图片就是裁剪的结果。
index.wxml:
<view class="page-section">
<text class="page-section__title">图片裁剪</text>
<image-cropper id="image-cropper" limit_move="{{true}}" disable_rotate="{{true}}" width="{{width}}" height="{{height}}" imgSrc="{{src}}" bindload="cropperload" bindimageload="loadimage" bindtapcut="clickcut"></image-cropper>
</view>
index.js:
Page({
/**
* 页面的初始数据
*/
data: {
src: '',
width: 250, //宽度
height: 250, //高度
},
startCuting() {
//获取到image-cropper对象
this.cropper = this.selectComponent("#image-cropper");
//开始裁剪
this.setData({
src: "https://cdn.nlark.com/yuque/0/2020/jpeg/1252071/1590847767698-f511e86d-f183-4f75-a04d-1b99cd9f0bd7.jpeg",
});
wx.showLoading({
title: '加载中'
})
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
this.startCuting()
},
cropperload(e) {
console.log("cropper初始化完成");
},
loadimage(e) {
console.log("图片加载完成", e.detail);
wx.hideLoading();
//重置图片角度、缩放、位置
this.cropper.imgReset();
},
clickcut(e) {
console.log(e.detail);
console.log(e.detail.url)
//点击裁剪框阅览图片
wx.previewImage({
current: e.detail.url, // 当前显示图片的http链接
urls: [e.detail.url] // 需要预览的图片http链接列表
})
}
})
index.json:
{
"usingComponents": {
"image-cropper": "../../../components/image-cropper/index"
}
}
本文由 caroly 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载 / 出处外,均为本站原创或翻译,转载前请务必署名
原文链接:https://caroly.fun/archives/miniprogram二组件二
最后更新:2021-05-04 23:52:57
Update your browser to view this website correctly. Update my browser now