(转载请注明出处,本文章内容仅用于学习)
这几天开发小程序有个新需求,需要在聊天群中实现发送图片。
之前做聊天时已经把框架搭好,心想应该只需要后端增加上传图片的一系操作。
当然最后做出来是没问题,但是不论用哪个小程序的图片自动缩放/截取,显示效果都差强人意,最后综合考虑(百度了一大圈都没有),只能手写图片自适应算法
最终想要实现的效果
目前这种聊天群的图片显示,用户最习惯的还是微信跟QQ的显示模式,参考微信的图片最终样式大体上划分为缩放与截取。
一种是不管图片原来有多大,发送后都会缩放到正常比例显示给用户,另一种通常是图片的宽高比例相差极大,如果缩放到用户的能一眼看完的程度,通常很不美观,所以一般会截取多余长/宽的那一部分,只显示一部分,用户如果想看完整图片需点击查看。
最后要能对图片的样式进行控制跟原来的一样。
解决方案
简略:使用js获取图片宽高并使用算法得出合理的显示宽高,最后将宽高动态赋值给wxml的图片,截取则利用图片定义的视窗区域。
一张图片在聊天室发出后,怎么知道这张图片要不要缩放/裁剪?这里做法是相对于屏幕,定义一块图片最大能显示多少的视窗区域,如果图片宽高任意一边超出这个视窗区,就要进行缩放操作,至于怎么缩放以及缩放多少合适请看代码。
考虑使用场景在数据量很多的消息列表中,最终选择将整体代码样式封装进组件中使用,实测使用组件加载图片跟不使用加载基本无差别。
组件代码
这里是针对聊天场景调试优化的版本,下面有通用版,不会创建组件的搜别人贴子,这里不做演示直接贴代码
JS
Component({
lifetimes:{
detached: function() {
//console.log("组件实例被从页面节点树移除");
},
attached: function(){
//console.log("组件实例进入页面节点树");
var that = this;
wx.getImageInfo({
src: that.properties.imgUrl,
success: function (res) {
var imgWidth=res.width;
var imgHeight=res.height;
var maxPictureViewWidth = 280;//图片视窗最大宽度
var maxPictureViewHeight = 220;//图片视窗最大高度
var ratio = imgWidth/imgHeight;
if(ratio<0.25 || ratio>2.2){//图片宽高比相差过大,可能需要截取
//判断图片是过高还是过宽
if(imgWidth>imgHeight){//图片过宽
if(imgHeight>maxPictureViewHeight){//同时判断图片高度有没有大于视窗最大高度
//图片过宽且图片高度大于视窗最大高度,依据高度缩减图片比例到视窗可见范围,宽度不管直接靠视窗截取多的部分不显示
var product = maxPictureViewHeight/imgHeight;
imgHeight=imgHeight*product
imgWidth=imgWidth*product
that.setData({
viewHeight:maxPictureViewHeight<imgHeight/2?maxPictureViewHeight:imgHeight/2,
viewWidth:maxPictureViewWidth<imgWidth/2?maxPictureViewWidth:imgWidth/2,
imgHeight:imgHeight/2,
imgWidth:imgWidth/2
})
}else{//图片高度小于视窗最大高度
that.setData({
viewHeight:imgHeight/2,
viewWidth:maxPictureViewWidth,
imgHeight:imgHeight/2,
imgWidth:imgWidth/2
})
}
}else{//图片过高
if(imgWidth>maxPictureViewWidth){
//图片过高且图片宽度大于视窗最大宽度,按照宽度缩减图片比例到视窗可见范围,高度不管直接靠视窗截取不显示
var product = maxPictureViewWidth/imgWidth;
imgHeight=imgHeight*product
imgWidth=imgWidth*product
that.setData({
viewHeight:maxPictureViewHeight,
viewWidth:maxPictureViewWidth,
imgHeight:imgHeight,
imgWidth:imgWidth
})
}else{//图片宽度小于视窗最大宽度
that.setData({
viewHeight:maxPictureViewHeight,
viewWidth:imgWidth/2,
imgHeight:imgHeight/2,
imgWidth:imgWidth/2
})
}
}
}else{//图片宽高比相差不大,按照原图比例缩放显示
//console.log("图片宽高比相差不大");
if(imgWidth > imgHeight){//图片宽度多,图片可能是正方形,宽高比例为1,不过没关系进哪个判断都一样
//console.log("图片宽度多")
if(imgWidth>maxPictureViewWidth){//图片宽度比高度多且宽度大于视窗最大宽度,进行等比缩放
console.log("图片宽度大于视窗最大宽度")
var product = maxPictureViewWidth/imgWidth;
imgWidth = imgWidth*product
imgHeight = imgHeight*product;
that.setData({
imgWidth:imgWidth/1.5,
imgHeight:imgHeight/1.5,
viewWidth:maxPictureViewWidth<imgWidth/1.5?maxPictureViewWidth:imgWidth/1.5,
viewHeight:maxPictureViewHeight<imgHeight/1.5?maxPictureViewHeight:imgHeight/1.5
})
}else{//图片宽度比高度多判断宽度有没有大于视窗宽度,没大于不进行缩放直接显示
//console.log("图片宽度不大于视窗最大宽度")
that.setData({
imgWidth:imgWidth/2,
imgHeight:imgHeight/2,
viewWidth:imgWidth/2,
viewHeight:imgHeight/2
})
}
}else{//图片高度多
// console.log("图片高度多");
if(imgHeight>maxPictureViewHeight){
//console.log("图片高度大于视窗最大高度");
var product = maxPictureViewHeight/imgHeight;
if(imgWidth>maxPictureViewWidth){
product = maxPictureViewHeight/imgHeight;
}
imgWidth = (imgWidth*product).toFixed(1);
imgHeight = (imgHeight*product).toFixed(1);
that.setData({
imgWidth:imgWidth,
imgHeight:imgHeight,
viewWidth:imgWidth,
viewHeight:maxPictureViewHeight
})
}else{
that.setData({
imgWidth:imgWidth,
imgHeight:imgHeight,
viewWidth:imgWidth,
viewHeight:imgHeight
})
}
}
}
}
})
},
moved: function(){
// console.log("组件实例被移动到节点树另一个位置");
},
created: function(){
// console.log("组件实例刚刚被创建时执行");
},
ready: function() {
// console.log("组件布局完成");
},
},
methods:{
pImg:function(e){
var imgurl = e.target.dataset.imgurl;
var list = [];
list.push(imgurl);
wx.previewImage({
urls: list,
})
},
},
pageLifetimes:{
hide: function() {
// console.log("组件所在页面隐藏");
},
},
options: {
multipleSlots: true,
addGlobalClass: true
},
properties: {
imgUrl:{
type: String,
value: ''
}
},
data: {
viewWidth:0,
viewHeight:0,
imgWidth:0,
imgHeight:0,
},
});
WXML
<view style="width:{{viewWidth}}px;height:{{viewHeight}}px;overflow:hidden;border-radius: 5px;">
<image style="width:{{imgWidth}}px;height:{{imgHeight}}px;" bindtap="pImg" src="{{imgUrl}}" data-imgurl="{{imgUrl}}">image>
view>
WXSS跟JSON啥都没有就不贴
使用方法
在要使用组件的页面对应的JSON文件中进行操作:引用组件
{
"usingComponents": {
"adaptivePicture": "/component/adaptivePicture/adaptivePicture"
}
}
然后在页面中,于要使用自适应图片的位置使用以下标签即可
<adaptivePicture imgUrl="图片地址">adaptivePicture>
注意:此处图片需要js动态获取图片宽高,如果图片地址直接使用io流会报错,想要使用需要用小程序对应接口把图片缓存下来生成链接再填入(接口叫啥忘了,这样做可能有性能隐患,不建议使用)
图片最大视窗区域
位于组件JS代码最上面的一块,设图片长宽最多能显示多少,如果图片长的一边超过这个视窗,会被缩放/裁剪到这个能容下在视窗内,所以针对场景规划合理的视窗对样式有能多的帮助(正常来讲应该使用用户手机屏幕的宽高,使用百分比来动态控制视窗区域而不是固定的像素,但考虑小程序连横屏都没有,用户屏幕宽度也都差不多,使用场景也差不多,就不画蛇添足了。不会真有人拿平板用小程序吧,不会吧不会吧)
可以看到最终效果跟设想的已经差不多了,样式也可以增加上
通用版图片自适应
JS
Component({
lifetimes:{
detached: function() {
//console.log("组件实例被从页面节点树移除");
},
attached: function(){
//console.log("组件实例进入页面节点树");
var that = this;
wx.getImageInfo({
src: that.properties.imgUrl,
success: function (res) {
var imgWidth=res.width;
var imgHeight=res.height;
var maxPictureViewWidth = 360;//图片视窗最大宽度
var maxPictureViewHeight = 220;//图片视窗最大高度
var ratio = imgWidth/imgHeight;
if(ratio<0.25 || ratio>2.2){//图片宽高比相差过大,可能需要截取
//判断图片是过高还是过宽
if(imgWidth>imgHeight){//图片过宽
if(imgHeight>maxPictureViewHeight){//同时判断图片高度有没有大于视窗最大高度
//图片过宽且图片高度大于视窗最大高度,依据高度缩减图片比例到视窗可见范围,宽度不管直接靠视窗截取多的部分不显示
var product = maxPictureViewHeight/imgHeight;
imgHeight=imgHeight*product
imgWidth=imgWidth*product
that.setData({
viewHeight:maxPictureViewHeight,
viewWidth:maxPictureViewWidth,
imgHeight:imgHeight,
imgWidth:imgWidth
})
}else{//图片高度小于视窗最大高度
that.setData({
viewHeight:imgHeight,
viewWidth:maxPictureViewWidth,
imgHeight:imgHeight,
imgWidth:imgWidth
})
}
}else{//图片过高
if(imgWidth>maxPictureViewWidth){
//图片过高且图片宽度大于视窗最大宽度,按照宽度缩减图片比例到视窗可见范围,高度不管直接靠视窗截取不显示
var product = maxPictureViewWidth/imgWidth;
imgHeight=imgHeight*product
imgWidth=imgWidth*product
that.setData({
viewHeight:maxPictureViewHeight,
viewWidth:maxPictureViewWidth,
imgHeight:imgHeight,
imgWidth:imgWidth
})
}else{//图片宽度小于视窗最大宽度
that.setData({
viewHeight:maxPictureViewHeight,
viewWidth:maxPictureViewWidth,
imgHeight:imgHeight,
imgWidth:imgWidth
})
}
}
}else{//图片宽高比相差不大,按照原图比例缩放显示
//console.log("图片宽高比相差不大");
if(imgWidth > imgHeight){//图片宽度多,图片可能是正方形,宽高比例为1,不过没关系进哪个判断都一样
//console.log("图片宽度多")
if(imgWidth>maxPictureViewWidth){//图片宽度比高度多且宽度大于视窗最大宽度,进行等比缩放
//console.log("图片宽度大于视窗最大宽度")
var product = maxPictureViewWidth/imgWidth;
if(imgHeight>maxPictureViewHeight){
product = maxPictureViewHeight/imgHeight;
}
imgWidth = (imgWidth*product).toFixed(1);
imgHeight = (imgHeight*product).toFixed(1);
that.setData({
imgWidth:imgWidth,
imgHeight:imgHeight,
viewWidth:maxPictureViewWidth,
viewHeight:maxPictureViewHeight
})
}else{//图片宽度比高度多判断宽度有没有大于视窗宽度,没大于不进行缩放直接显示
//console.log("图片宽度不大于视窗最大宽度")
that.setData({
imgWidth:imgWidth,
imgHeight:imgHeight,
viewWidth:maxPictureViewWidth,
viewHeight:maxPictureViewHeight
})
}
}else{//图片高度多
// console.log("图片高度多");
if(imgHeight>maxPictureViewHeight){
//console.log("图片高度大于视窗最大高度");
var product = maxPictureViewHeight/imgHeight;
if(imgWidth>maxPictureViewWidth){
product = maxPictureViewHeight/imgHeight;
}
imgWidth = (imgWidth*product).toFixed(1);
imgHeight = (imgHeight*product).toFixed(1);
that.setData({
imgWidth:imgWidth,
imgHeight:imgHeight,
viewWidth:maxPictureViewWidth,
viewHeight:maxPictureViewHeight
})
}else{
that.setData({
imgWidth:imgWidth,
imgHeight:imgHeight,
viewWidth:maxPictureViewWidth,
viewHeight:maxPictureViewHeight
})
}
}
}
}
})
},
moved: function(){
// console.log("组件实例被移动到节点树另一个位置");
},
created: function(){
// console.log("组件实例刚刚被创建时执行");
},
ready: function() {
// console.log("组件布局完成");
},
},
methods:{
pImg:function(e){
var imgurl = e.target.dataset.imgurl;
var list = [];
list.push(imgurl);
wx.previewImage({
urls: list,
})
},
},
pageLifetimes:{
hide: function() {
// console.log("组件所在页面隐藏");
},
},
options: {
multipleSlots: true,
addGlobalClass: true
},
properties: {
imgUrl:{
type: String,
value: ''
}
},
data: {
viewWidth:0,
viewHeight:0,
imgWidth:0,
imgHeight:0,
},
});
WXML跟上面的基本一致
<view style="width:{{viewWidth}}px;height:{{viewHeight}}px;overflow:hidden;border-radius:1%;">
<image style="width:{{imgWidth}}px;height:{{imgHeight}}px;border-radius:1%;" catchtap="pImg" src="{{imgUrl}}" data-imgurl="{{imgUrl}}">image>
view>
两个区别:聊天室的那个当图片超过视窗显示区域,缩放后会比视窗显示区域还要小1一倍左右
通用版当图片超过视窗显示区域等比缩放后,图片长边宽/高跟视窗显示区域的宽/高一致
所以通用版更适合用在图片本身为主体不需要贴合其他元素的场景(如帖子封面,缩略图)
最后有不同看法,改进方法欢迎留言讨论,希望帖子帮到大家哈哈