原生图片预览实现及由此引出的图片自适应宽高问题探索

  • A+
所属分类:Web前端
摘要

→→推荐一套B站新鲜出炉的web前端教程←←  看到手机相册,突发奇想:能不能用实现一个原生的页面“相册”?或者说,传统网页中怎么实现图片预览功能?

→→推荐一套B站新鲜出炉的web前端教程←←

 

看到手机相册,突发奇想:能不能用实现一个原生的页面“相册”?或者说,传统网页中怎么实现图片预览功能?

如果在原生网页中使用插件,那必不可少的要引入一堆东西(并不是鄙视插件,只是为了引入下文,见谅嘿嘿);但又不是说所有的页面都要用框架…

经过一番探索,终于大概实现了想要的功能:
原生图片预览实现及由此引出的图片自适应宽高问题探索
大概流程就是:可以点开大图观看、可以左右滑动切换、进入预览时可以从你当前点击的那一张开始浏览。


实现相册初始展示页

如上所示,我们可以在Header头中添加Viewport配置 —— 移动端页面常备:

<meta name="viewport" content="width=device-width, initial-scale=1">

然后在Body元素中添加小相片列表,其HTML如下:

<div class="gallery">     <div class="item">         <img src="images/39.jpg" alt="1">     </div>     <div class="item">         <img src="images/download.png" alt="2">     </div>     <div class="item">         <img src="images/nan.png" alt="3">     </div>     <div class="item">         <img src="images/nan2.png" alt="4">     </div>     <div class="item">         <img src="images/timg.jpg" alt="5">     </div> </div>

 

现在页面上是一张张大小不一的图片凌乱排列,然后我们给它添加样式:

.gallery{     /* 设置设置相册的宽度为屏幕宽度 */     width:100vw;     /* 相册采用flex布局 */     display: flex;     /* 相册每一项为横向排列,并且换行 */     flex-flow: row wrap; }  .gallery .item{     /* 每一项平均排列 */     /* flex: 1; */     /* 图片宽度设置为1/3屏幕宽度 */     width:calc(100vw / 3);     overflow: hidden; }  .gallery .item img{     width: 100%;     height: 100%; }

 

熟悉笔者的都知道:笔者提倡“尽可能的使用CSS去解决问题!”。所以:我们这里就要考虑这样一个问题:预览时页面的排布和原来有什么不同之处?

我的思路是:开始时总的宽度是100vw,子元素(相册图片)以flex排列;点击某一个图片预览时动态给父元素添加一个类名,这个类的作用是:将父元素下(单张)图片的最大宽度设为100vw(子元素从始至终都不设宽)。然后在js中根据子元素的长度计算其“真正宽度”(.style.width)。并根据当前点击的是第几个子元素计算应该transform偏移多少距离:

.gallery.preview{     background-color: gray; }  /** 添加过渡效果 */ .gallery.animation{     -webkit-transition: 1s ease;     -moz-transition: 1s ease;     -o-transition: 1s ease;     transition: 1s ease; }  .gallery.preview .item{     /* 对子项设置为flex布局 */     display: flex;     /* 设置margin为auto实现图片居中显示 */     margin: auto;     align-items: center;          width: 100vw;     height: 100vh;     overflow: hidden; }  .gallery.preview .item img {     /* 设置预览图片的最大宽度为屏幕宽度 */     max-width: 100vw;     /* 设置预览图片的最大高度为屏幕高度 */     max-height: 100vh;     /* 初始化图片宽高,覆盖之前设置的宽高 */     width: initial;     height: initial; }

 

根据上面的思路,在HTML页面加入脚本,监听Click事件:用户单击相片时,将相册切换为预览模式:

var $gallery = document.querySelector(".gallery"); $gallery.addEventListener("click", function (e) {     // 监听单击事件,切换相册的CSS class来实现预览和普通模式的切换     var classList = $gallery.classList,         css_preview = "preview";     if (classList.contains(css_preview)) {         classList.remove(css_preview)          // 在非预览模式下,相册的宽度为100vw         $gallery.style.width = 100 + "vw";         //【1】     } else {         classList.add(css_preview);         // 进入在预览模式,所有的图片横着拍成一排,相册的总宽度为每一个项目长度总和。         $gallery.style.width = 100 * itemsLength + "vw";         //【2】     } });

 

在预览模式下,通过设置gallery样式类元素的宽度,让相册图片排成一排,然后通过CSS3的transform属性,设置元素的偏移量,移动整个元素位置,使得需要展示的图片出现在屏幕主区域。

在开始移动之前,我们要先禁止掉浏览器默认的触摸行为 —— 用CSS来做:

//为类“.gallery.preview”添加属性 touch-action: none;

 

然后去监听touchstart、touchmove及touchend事件来实现手势滑动功能

这三个事件很是常用,但其实他们的原理很简单,总结来说就是:在start(刚按下)时记录此时的手指位置——作为初始值;在move(触摸滑动)时根据实时的手指位置和初始手指位置变量实现要求判断;在end(手指离开)时(也有直接在move时进行的)进行收尾工作——比如:图片滑动完全划过去、元素跑到结束位置、将事件监听取消;

var isTtouchstart = false,     startOffsetX,     currentTranX = 0,     width = $gallery.offsetWidth,     $items = $gallery.querySelectorAll(".item"),     itemsLength = $items.length,     move = function (dx) {         $gallery.style.transform = "translate(" + dx + "px, 0)";     }; $gallery.addEventListener("touchstart", function (e) {     // 触摸开始时,记住当前手指的位置     startOffsetX = e.changedTouches[0].pageX;     //      $gallery.classList.remove("animation"); });  $gallery.addEventListener("touchmove", function (e) {     isTtouchstart = true;     // 计算手指的水平移动量     var dx = e.changedTouches[0].pageX - startOffsetX;     // 调用move方法,设置galley元素的transform,移动图片     move(currentTranX + dx); });  $gallery.addEventListener("touchend", function (e) {     if (isTtouchstart) {         // 在移动图片的时候,需要动画,动画采用CSS3的transition实现。         $gallery.classList.add("animation");         // 计算偏移量         var dx = e.changedTouches[0].pageX - startOffsetX;         // 如果偏移量超出gallery宽度的一半         if (Math.abs(dx) > width / 2) {             // 处理临界值             if (currentTranX <= 0 && currentTranX > -width * itemsLength) {                 // 如果手指向右滑动                 if (dx > 0) {                     // 如果图片不是显示第一张                     if (currentTranX < 0) {                         currentTranX = currentTranX + width;                     }                 //  如果手指向右滑动,并且当前图片不是显示最后一张                 } else if (currentTranX > -width * (itemsLength - 1)) {                     currentTranX = currentTranX - width;                 }             }         }         // 如果未超出图片宽度的一半,上述条件不会执行,而这个时候,手指在移动的时候,图片随着手指移动了,通过下面的代码,将图片的位置还原         // 如果超出了图片宽度的一半,将切换到上一张/下一张图片         move(currentTranX);     }     isTtouchstart = false; });

 

到此为止,基本功能是实现了,不过感觉少了点什么:
原生图片预览实现及由此引出的图片自适应宽高问题探索
啊,知道了:点击进入预览时始终是从第一张开始的,而且如果结束预览时不是第一张它会消失!

这显然是没有做“变量恢复”:

//在上面代码中【1】的位置添加: currentTranX = 0; $gallery.style.transform = "translate(0, 0)";  //在上面代码【2】的位置添加: for(let i in $items){     if($items[i]==e.target.parentNode){         currentTranX=-(+i)*width         move(currentTranX)         break     } }

 

于是就出现了文章开头所示效果!


这个小demo是结束了,但是有一点却引起了我的关注:上面图片排列时为了防止展示问题都是让外层父容器指定宽高,然后给img元素一个宽高100%。

有没有可能让img固定宽高比呢?
能不能在所有外部宽高下都保持此宽高比呢?

padding-bottom实现比例固定图片自适应

calc() 和 background-size: cover; 都是个不错的想法,但是往兼容性、清晰度和特殊情况一看就会坏菜——比如cover在缩到一定范围时就会有部分遮盖。

但是如果padding出马,就像这样:

<div class="banner">   <img src="./images/nan.png"> </div>

.banner {     padding-bottom: 60%;     position: relative; } .banner > img {     position: absolute;     width: 100%;      height: 100%;     left: 0; top: 0; }

 

原生图片预览实现及由此引出的图片自适应宽高问题探索
Look:如此丝滑!
可以看到,无论屏幕宽度多宽,图片比例都是固定的——不会有任何剪裁,不会有任何区域缺失,布局就显得非常有弹性,也更健壮。

对于这种图片宽度100%容器,高度按比例的场景,padding-bottom的百分比值大小就是图片元素的高宽比,就这么简单。

重要的来了: 有时候图片宽度并不是容器的100%,例如,图片宽度50%容器宽度,图片高宽比4:3,此时用padding-bottom来实现就显得666了:

/**右50%表示宽度,下66.66%表示“高宽比4:3”**/ padding: 0 50% 66.66% 0;

object-fit实现图片自适应容器

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>宽高自适应</title>     <style>         .img-container{             width:688px;             height:204px;             background: black;         }            .img-container img{             width: 100%;             height: 100%;             object-fit: contain;         }     </style> </head> <body>     <div class="img-container">         <img src="images/download.png" alt="原生图片预览实现及由此引出的图片自适应宽高问题探索" alt="4">     </div> </body> </html>

 

object-fit 似乎一定程度上解决了width:100%带来的一些图片宽高比问题,又结合了background-size:cover 的固定宽高比伸缩,也是比较秀的了。

“图片预览”应用场景下的js应用

其实更多场景下,看的是“图片完全、优雅地展示出来”。这时候,其实可以用JavaScript动态计算图片宽高:

  1. 容器宽高比例 > 图片宽高比例:说明图片比较高,以高度为准,宽度适应
  2. 容器宽高比例 < 图片宽高比例:说明图片比较宽,以宽度为准,高度适应
<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>Document</title>     <style>         div + div {             margin-top: 30px;         }         .img-container{             width:688px;             height:304px;             overflow: hidden;             display: flex;             justify-content: center;             align-items: center;             box-sizing: border-box;             background: black;         }         .width {             width: 668px;             height: 404px;         }     </style> </head> <body>     <div class="img-container"></div>     <script>         const container = document.querySelector(".img-container")         container.classList.add("width")         const url = 'images/nan.png';         const img = document.createElement("img")         img.src = url;         img.onload=function(){             let {width,height} = getRealSize(container.offsetWidth,container.offsetHeight,img.width,img.height, 1)             img.width = width             img.height = height         }         container.appendChild(img)                  /**          * [获取自适应图片的宽高]          * @param  {[number]} parentWidth  [父容器宽]          * @param  {[number]} parentHeight [父容器高]          * @param  {[number]} imgWidth     [图片实际宽]          * @param  {[number]} imgHeight    [图片时间高]          * @param  {[number]} radio        [撑开比例]          * @return {[Obejt]}              [图片真实宽高]          */         function getRealSize(parentWidth, parentHeight, imgWidth, imgHeight, radio){             let real = {width:0,height:0}             let scaleC = parentWidth / parentHeight;             let scaleI = imgWidth / imgHeight;             if(scaleC > scaleI){  //说明图片比较高 以高度为准                 real.height = radio * parentHeight;                 real.width = parentHeight * scaleI;             }else if(scaleC < scaleI){  //说明图片比较宽 以宽度为准                 real.width = radio * parentWidth;                 real.height = parentWidth / scaleI;             }else{                 real.width = radio * parentWidth;                 real.height = parentWidth / scaleI;             }             return real         }     </script> </body> </html>