用Vue實現一個掘金沸點圖片展示元件

超級索尼發表於2019-03-04

動機

最近的後臺專案裡需要新增新鮮事功能,簡而言之就是一個帶圖片的評論回覆系統,看了好幾個類似的系統後,還是決定仿照掘金沸點的設計,簡潔而且優雅,整個模組介面基本和沸點一樣,只是少了一些功能(連結和話題功能沒有做)

用Vue實現一個掘金沸點圖片展示元件

整個系統比較複雜,包含圖片文字上傳元件,emoji表情元件,一級評論,二級回覆,以及二級回覆要能展示圖片,點贊元件,圖片展示元件等。其實主要是後臺比較複雜,如何有效地設計資料庫表結構以及各種增刪評論。本文主要介紹下圖片展示模組的構思和程式碼編寫邏輯,如下圖,下圖是新鮮事中的圖片展示介面(縮圖),當使用者在釋出新鮮事時上傳了圖片時,下面的新鮮事元件就會展示所有圖片.

用Vue實現一個掘金沸點圖片展示元件

感覺這個圖片展示元件邏輯較多且比較複雜,也找不到別人的輪子,因此值得拿出來分析下,下面的分析只會給出關鍵的程式碼,不會給出全部程式碼,主要太多太複雜,理解原理就能夠自己做一個

元件功能分析

首先接到這個任務第一步是仔細檢視掘金沸點的圖片展示元件的具體表現和邏輯(可以去沸點模組試一試),這一步需要大量測試來挖掘其中的所有邏輯情況,下面列舉出該元件的一些表現
(1)如果只有一張圖,則顯示為大圖,如下圖

用Vue實現一個掘金沸點圖片展示元件

(2)如果有多張圖(最多9張),則顯示為縮圖,且圖片數量不同時圖片排列不同

用Vue實現一個掘金沸點圖片展示元件

上圖是8張圖時的排列,當9張圖時排列呈現九宮格形式,下圖是5張圖的排列形式

用Vue實現一個掘金沸點圖片展示元件

區別在於每一行的圖片個數隨著圖片總數的變化而變化

(3)當某張圖長寬比超過一定閾值時,圖片右下角顯示長圖示籤

用Vue實現一個掘金沸點圖片展示元件

(4)點選任意一張縮圖,切換到詳情圖展示介面

用Vue實現一個掘金沸點圖片展示元件

上圖的詳情介面中,主要包含頂部操作欄4個按鈕以及底部的縮圖欄,點選任何一張縮圖能跳轉到對應圖片,然後滑鼠在大圖左側時顯示上一張的cursor,右側時顯示下一張的cursor並能夠切換圖片。點選旋轉按鈕能夠旋轉圖片

(54)點選上面圖片中的檢視大圖按鈕進入到全屏的大圖檢視元件

用Vue實現一個掘金沸點圖片展示元件

該元件能夠左右切換圖片,且如果展示的是長圖,首先在視口內完整顯示長圖,如下圖

用Vue實現一個掘金沸點圖片展示元件

然後點選螢幕,切換到長圖原始尺片顯示介面(右側出現滾動條),如下圖

用Vue實現一個掘金沸點圖片展示元件

再次點選則切換回視口內長圖的完整顯示介面

元件結構抽象

首先給整個元件命名(新鮮事圖片展示元件),只需要一個prop即圖片陣列,包含每張圖片的url
<message-image-viewer :imageList="imageList"></message-image-viewer>

經過上面的功能分析,發現全屏圖片檢視這個模組可以抽象成一個單獨的元件,這個元件是上面元件的子元件

<!--全屏大圖元件-->
<full-screen-viewer
  :imageList="imageList"
  :currentImageIndex="currentImageIndex"
  @close="handleFullScreenViewerClose"
  v-if="isShowFullScreenViewer">
</full-screen-viewer>
複製程式碼

需要2個prop,首先是要展示的圖片陣列,其次是當前展示圖片的index,由其父元件<message-image-viewer>傳入

下面是<message-image-viewer>的總體結構

<template>
  <div class="wrapper" ref="wrapper">
  
    <!--縮圖的div-->
    
    <div class="brief-view-wrapper" v-show="!isShowDetail" ref="outerWrapper">
      <!--如果是單張圖-->
      <template v-if="isSingleImage">
        <div class="single-img-container"
             @click="showDetailImage(imageList[0])"
             :style="{backgroundImage:`url(`+imageList[0]+`)`}">
          <!--用div撐開圖片-->
          <div class="ratio-holder" :style="{paddingTop: calcSingleImgHeight}">
          </div>
          <span class="long-image" v-if="isLongImage">長圖</span>
        </div>
      </template>
      <!--多張圖-->
      <template v-else>
        <!--圖片放在backgroundImage屬性中-->
        <div class="multiple-img-wrapper" :class="[colsOfMultipleImages]">
          <div class="multiple-img-container"
               v-for="(item,index) in imageList"
               @click="showDetailImage(item)"
               :style="{backgroundImage:`url(`+item+`)`}">
            <!--控制圖片的高度和寬度一樣,padding-top基於父元素的寬度-->
            <div class="ratio-holder" style="padding-top: 100%">
            </div>
            <span class="long-image" v-show="isLongImageList[index]">長圖</span>
          </div>
        </div>
      </template>
    </div>
    
    <!--詳情圖的div-->
    
    <div class="detail-view-wrapper" v-show="isShowDetail" ref="detailViewWrapper">
      <!--頂部操作欄-->
      <div class="top-panel">
        <div class="panel-item" @click="hideDetailImage">
          <i class="iconfont icon-zoomout icon-pos"></i>
          <span>收起</span>
        </div>
        <div class="panel-item" @click="showFullScreenViewer">
          <i class="iconfont icon-zoomin icon-pos"></i>
          <span>檢視大圖</span>
        </div>
        <div class="panel-item" @click="handleImageRotate(-1)">
          <!--inline-block才能旋轉,inline不行-->
          <i class="iconfont icon-reload icon-pos"
             style="transform: rotateY(-180deg);display:inline-block;">
          </i>
          <span>向左旋轉</span>
        </div>
        <div class="panel-item" @click="handleImageRotate(1)">
          <i class="iconfont icon-reload icon-pos"></i>
          <span>向右旋轉</span>
        </div>
      </div>
      <!--中間圖片展示欄,注意需要設定高度,因為裡面的img是絕對定位-->
      <div class="detail-img-wrapper" :style="{height:outerDivHeight+`px`}">
        <!--載入的logo-->
        <div class="detail-img-loading">
          <circle-loading fillColor="#9C9C9C" v-if="!isDetailImageLoaded">
          </circle-loading>
        </div>
        <img src=""
             ref="detailImage"
             v-show="isDetailImageLoaded"
             :style="detailImageStyle"
             class="detail-img">
        <!--點選隱藏詳情圖的div-->
        <div class="toggle-zoomout" @click="hideDetailImage">
        </div>
        <!--上一張圖片的div-->
        <div class="prev-img" @click="switchImage(-1)" v-if="currentImageIndex > 0">
        </div>
        <div class="next-img" @click="switchImage(1)" v-if="currentImageIndex<imageList.length-1">
        </div>
        <!--全屏大圖元件-->
        <full-screen-viewer
          :imageList="imageList"
          :currentImageIndex="currentImageIndex"
          @close="handleFullScreenViewerClose"
          v-if="isShowFullScreenViewer">
        </full-screen-viewer>
      </div>
      
      <!--縮圖展示欄-->
      
      <div class="small-img-wrapper">
        <div class="small-img-container"
             @click="switchSmallImage(index)"
             :class="{`small-img-active`:currentImageIndex === index}"
             :style="{backgroundImage:`url(`+item+`)`}"
             v-for="(item,index) in imageList">
        </div>
      </div>
    </div>
  </div>
</template>
複製程式碼

是不是感覺很複雜,其實一開始的結構也是很簡單,慢慢加程式碼就變成現在這樣,主要結構圖如下

用Vue實現一個掘金沸點圖片展示元件

主體就是詳情圖的div和縮圖的div進行切換,通過內部變數isShowDetail進行切換,然後全屏大圖元件在詳情圖內

縮圖部分分析

下面先分析縮圖部分,縮圖就是上面所說的圖片的縮圖展示的模樣,由前面分析得知單張圖時顯示大圖,多張圖時顯示小圖,最多9張圖。整體結構如下

用Vue實現一個掘金沸點圖片展示元件

用一個isSingleImage變數來控制顯示單圖還是多圖的縮略,它是個計算屬性,當prop傳入的圖片張數小於等於1時為true

//是否是單張圖
isSingleImage:function(){
  return !(this.imageList.length>1)
},
複製程式碼

1張圖時顯示大圖,這個邏輯看似簡單,其實規則比較複雜,下面是我經過測試得出的單圖顯示規則:

(1)首先判斷應該顯示為豎圖還是橫圖,根據原圖的長寬比來決定

(2)所有圖片寬度width一定,變的只有高度

(3)高度/寬度超過一定值(1.8)顯示長圖示籤,此時顯示的圖片的高度為寬度的1.45倍,否則按比例顯示

(4)如果高度/寬度小於一定值(0.68),則縮圖外框高度高度為寬度的0.68倍,且居中顯示,否則按比例顯示

這些規則都是必須的,比如給一張豎直方向很長的圖,那麼其按上述規則顯示為如下

用Vue實現一個掘金沸點圖片展示元件

上圖中第一張圖是該長圖水平放置,下面的是該長圖豎直放置,顯示長圖示籤,他們寬度都是一定值,特別注意第四條,如果該圖水平方向上很長,那麼必須用該規則讓其顯示的比例適中,不那麼難看。或者給你一張10×10畫素的圖片,顯示出來肯定不能按原比例顯示,得適當放大

下面是單圖的顯示邏輯程式碼

<!--如果是單張圖-->
<template v-if="isSingleImage">
    <div class="single-img-container"
         @click="showDetailImage(imageList[0])"
         :style="{backgroundImage:`url(`+imageList[0]+`)`}">
      <!--用div撐開圖片-->
      <div class="ratio-holder" :style="{paddingTop: calcSingleImgHeight}">
      </div>
      <span class="long-image" v-if="isLongImage">長圖</span>
    </div>
</template>
複製程式碼

圖片顯示是用的backgroundImage屬性而沒有用image標籤,感覺簡單點,需要設定background-size:cover以及background-position:50%保證圖片居中且div內充滿圖片不留白,當然圖片是會被裁剪,注意這裡的single-image-container類只設定了固定的寬度200px,其高度由裡面的div撐開,給ratio-holder類動態設定padding-top,padding-top的百分比值是取的是基於父元素寬度的百分比,因此寬度一定就可以計算出對應的高度,下面給出由上述規則實現的計算div高度的函式

//單張圖的高度計算
calcSingleImgHeight: function(){
    let self = this;
    let image = new Image();
    //獲取圖片的原始尺寸並計算比例
    //圖片較大的話必須等圖片載入完成才能獲取尺寸
    image.onload = function(){
      self.singleImageNaturalWidth = image.naturalWidth;
      self.singleImageNaturalHeight = image.naturalHeight;
    };
    image.src = this.imageList[0];
    let ratio = this.singleImageNaturalWidth?this.singleImageNaturalHeight / this.singleImageNaturalWidth : 1;
    if(ratio < this.imageMinHeightRatio){
      ratio = this.imageMinHeightRatio
    }
    if(ratio > this.imageMaxHeightRatio){
      // 該圖是長圖  
      this.isLongImage = true;
      ratio = this.imageMaxHeightRatio
    }
      return ratio*100+`%`;
},
複製程式碼

這個方法是個計算屬性,計算圖片的長寬比需要用到naturalWidth和naturalHeight,這2個值是圖片的原始寬高,但是必須等到圖片載入完成才能獲取到,否則就是0,因為是計算屬性,所以onload方法觸發時會重新計算圖片比例。

下面分析多圖情況下的縮圖顯示

<!--多張圖-->
<template v-else>
    <!--圖片放在backgroundImage屬性中-->
    <div class="multiple-img-wrapper" :class="[colsOfMultipleImages]">
      <div class="multiple-img-container"
           v-for="(item,index) in imageList"
           @click="showDetailImage(item)"
           :style="{backgroundImage:`url(`+item+`)`}">
        <!--控制圖片的高度和寬度一樣,padding-top基於父元素的寬度-->
        <div class="ratio-holder" style="padding-top: 100%">
        </div>
        <span class="long-image" v-show="isLongImageList[index]">長圖</span>
      </div>
    </div>
</template>
複製程式碼

多圖情況下的顯示規則:1-4張為col-4,5,6為col-3, 7,8張為col-4, 9張為col-3,col-n代表n列

下用一個v-for遍歷圖片陣列即可,圖片寬度高度相同,因此padding-top為100%,注意圖片張數不同時排列情況不同的邏輯是通過colsOfMultipleImages這個計算屬性根據圖片張數計算出對應的類名

//多圖時顯示的列數的類
colsOfMultipleImages:function(){
  let len = this.imageList.length;
  let map = {
      	1:`col-4`,2:`col-4`,3:`col-4`,4:`col-4`,
        5:`col-3`,6:`col-3`,7:`col-4`,8:`col-4`,
        9:`col-3`};
  return len===0?``:map[len]
},
複製程式碼

其實也就2個類,通過控制其寬度保證圖片計時換行排列

.col-3{
  width:75%
}
.col-4{
  width:100%;
}
複製程式碼

詳情圖分析

詳情圖主要涉及到圖片旋轉,稍微麻煩點

用Vue實現一個掘金沸點圖片展示元件
用Vue實現一個掘金沸點圖片展示元件

詳情圖主要分為3個部分,上面是頂部操作欄,中間是圖片展示欄,底部時縮圖展示欄,其中中間圖片展示欄同樣有其對應的圖片展示規則,經過分析如下

(1)如果圖片的寬度超過外層div的寬度,則寬度為div的寬度,高度按圖片比例縮放

(2)如果圖片的寬度未超過外層div的寬度,則圖片按原尺寸顯示,圖片水平居中

(3)大圖載入時的loading圖預設寬高是固定的,寬度為外層div的寬度,高度略小

注意如果載入一張很小的圖片,仍然按原比例顯示,掘金就是這麼做的,如下圖

用Vue實現一個掘金沸點圖片展示元件

下面主要分析下圖片旋轉功能的實現,略複雜,首先明確一點這裡的圖片旋轉是通過css的transform的rotate進行旋轉的,圖片只是視覺上旋轉了,本質上沒有,如果本質上旋轉要用canvas重新畫圖。
css的旋轉看似很簡單,比如右轉90度,只需要給圖片的style動態設定transform:rotate(90deg)即可?其實不然,這樣的旋轉只會導致圖片在原來的位置進行旋轉(transform-origin預設為center,圖片中心點),且圖片的寬高都不變,可以想象下一張很長的圖片旋轉成水平方向後的情況,明顯有問題,因此這裡的邏輯需要動態計算圖片寬高以及外層div寬高,最終結果如下動態圖所示

用Vue實現一個掘金沸點圖片展示元件

由上圖可見這種旋轉其實圖片寬高都有變化,不是單純的css旋轉,下面慢慢分析,html結構如下

<!--中間圖片展示欄,注意需要設定高度,因為裡面的img是絕對定位-->
<div class="detail-img-wrapper" :style="{height:outerDivHeight+`px`}">
    <!--載入的logo-->
    <div class="detail-img-loading">
      <circle-loading fillColor="#9C9C9C" v-if="!isDetailImageLoaded">
      </circle-loading>
    </div>
    <!--要展示的圖片-->
    <img src=""
         ref="detailImage"
         v-show="isDetailImageLoaded"
         :style="detailImageStyle"
         class="detail-img">
    <!--點選隱藏詳情圖的div-->
    <div class="toggle-zoomout" @click="hideDetailImage">
    </div>
    <!--上一張圖片的div-->
    <div class="prev-img" @click="switchImage(-1)" v-if="currentImageIndex > 0">
    </div>
    <div class="next-img" @click="switchImage(1)" v-if="currentImageIndex<imageList.length-1">
    </div>
    <!--全屏大圖元件-->
    <full-screen-viewer
      :imageList="imageList"
      :currentImageIndex="currentImageIndex"
      @close="handleFullScreenViewerClose"
      v-if="isShowFullScreenViewer">
    </full-screen-viewer>
</div>
複製程式碼

上面程式碼中<img>標籤就是要展示的圖片,由於圖片需要載入,因此設定一個circle-loading元件用於展示loading效果,當點選縮圖時執行下面的函式。剩下的div都是絕對定位,注意最外層的div動態繫結了高度,這是為了根據圖片高度的變化而改變外層div的高度,否則圖片會溢位div

  //展示詳情大圖
  showDetailImage: function(imgUrl){
  	let self = this;
  	//設定index
        this.currentImageIndex = this.imageList.indexOf(imgUrl);
  	//改變狀態為大圖載入中
  	this.isDetailImageLoaded = false;
  	//計算大圖的原始尺寸
        let image = this.$refs.detailImage;
        image.onload = function(){
          self.isDetailImageLoaded = true;
          self.detailImageNaturalWidth = image.naturalWidth;
          self.detailImageNaturalHeight = image.naturalHeight;
         };
        image.src = imgUrl;
        this.isShowDetail = true;
  },
複製程式碼

這裡主要做的事就是在圖片載入完成後記錄當前圖片的原始寬高,供後續旋轉使用。下面分析下圖片旋轉的整個過程,首先注意到img標籤新增了一個detail-img類,這個類的內容是

.detail-img{
    position: absolute;
    left:50%;
    top:0;
    transform-origin: top left;
}
複製程式碼

上面的css表示該圖片絕對定位,不佔文件空間,並且向右移動50%,變換的基本點設定在圖片左上角,這樣做下來的圖片顯示情況就如下圖

用Vue實現一個掘金沸點圖片展示元件

上圖的紅點就是圖片旋轉的基本點,該基本點在外層div的中點處,這麼做是為了方便旋轉的一系列處理,點選向右旋轉按鈕,圖片按照箭頭方向旋轉,注意圖片此時有一半在div外面,當向右旋轉後得到下圖

用Vue實現一個掘金沸點圖片展示元件

此時圖片的方向已經旋轉正確,但仍然有一部分在div外面,因此只旋轉是不行的,每次旋轉還必須伴隨著transform:translate進行圖片平移操作,讓圖片重新回到div內部,對於上面的圖片旋轉,旋轉後需要translate(0,-50%),即在x方向上不處理,y方向上移動-50%距離,這裡容易弄反,該圖片看著是需要水平位移,其實這已經是旋轉後的結果了,因此現在的水平位移就是旋轉前的垂直方向上的位移,所以translateY是-50%,繼續向右旋轉的話translateX和translateY的值會發生變化,根據推理可以得出一個陣列detailImageTranslateArray,儲存這旋轉時圖片需要位移的百分比,下面陣列中從左到右是順時針旋轉,每一項的第一個值是translateX的值,第二個是translateY的值

detailImageTranslateArray:[[`-50%`,`0`],[`0`,`-50%`],[`-50%`,`-100%`],[`-100%`,`-50%`]],
複製程式碼

圖片初始狀態是上述陣列的第一個值[-50%,0],因此我們可以根據圖片當前的translateX和y的值得到圖片旋轉後下一個狀態的translateX和y的值,然後再將該值繫結到圖片的style上即可完成旋轉

下面函式就是處理圖片旋轉的邏輯,點選向左或向右按鈕觸發下面函式

//處理圖片旋轉
handleImageRotate: function(dir){
      	//圖片載入完成才能旋轉
      	if(!this.isDetailImageLoaded)return
        // 注意旋轉中心是圖片的左上角(transform-origin:top left)
        let angleDelta = dir === 1?90:-90;
        //計算旋轉後的角度
        this.detailRotateAngel = (this.detailRotateAngel + angleDelta)%360;
        //修正translate的值
        let currentIndex;
        this.detailImageTranslateArray.forEach((item,index)=>{
          //找到當前的tranlate值
          if(item[0]===this.detailImageTranslateX && item[1]===this.detailImageTranslateY){
            currentIndex = index;
          }
        });
        //取下一個值
        let nextIndex = currentIndex+dir;
        if(nextIndex === this.detailImageTranslateArray.length){
          nextIndex = 0;
        }else if(nextIndex === -1){
          nextIndex = this.detailImageTranslateArray.length - 1;
        }
        //更新tranlate的值
        this.detailImageTranslateX = this.detailImageTranslateArray[nextIndex][0];
        this.detailImageTranslateY = this.detailImageTranslateArray[nextIndex][1];

        //修正外層div的高度
        this.processImageScaleInRotate();
},
複製程式碼

根據傳入的dir引數決定旋轉方向,然後計算出旋轉後的角度,再計算出旋轉後需要translate的值,因此,圖片的旋轉由data中3個值決定: detailRotateAngel,detailImageTranslateX ,detailImageTranslateY 分別代表旋轉角度,x方向上的位移,y方向上的位移,最後通過計算屬性將其繫結到img標籤上即可

//大圖的style,注意旋轉的時候必須重設寬高和translate值
  detailImageStyle:function(){
    return {
      width:this.detailImageWidth+`px`,
      height:this.detailImageHeight+`px`,
      //注意順序:先旋轉再移動
      transform:`rotate(`+this.detailRotateAngel+`deg)` +` `
                +`translate(`+this.detailImageTranslateX+`,`+this.detailImageTranslateY+`)`

    }
  },
複製程式碼

上面的計算屬性返回了一個style物件,裡面的transform由旋轉和平移組成,這裡要注意先rotate再translate,否則會出問題,經過上面的邏輯,圖片已經可以正常旋轉,但是有個巨大的問題,這裡的圖片雖然可以旋轉,但是其尺寸沒有自適應,比如一張非常長的圖由豎直方向變為水平方向後,其寬度必須不能超過最外層div的寬度,因此還要給圖片動態繫結width和height,見上述程式碼

圖片的寬高是2個計算屬性得來的

//詳情大圖的高度計算
detailImageHeight:function(){
    if(!this.isDetailImageLoaded){
      //載入時高度固定
    	return this.loadingDefaultHeight
    }else{
      return this.processImageScaleInRotate().height
    }
},
//詳情大圖的寬度
detailImageWidth:function(){
    if(!this.isDetailImageLoaded){
      //外層div的寬度
      let outerDiv = this.$refs.wrapper;
      let clientWidth = outerDiv?outerDiv.clientWidth:1;
      return clientWidth
    }else{
      return this.processImageScaleInRotate().width
    }
},
複製程式碼

這裡又分為載入中和非載入狀態的計算,如果圖片是處於載入中,則高度固定,寬度為外層div的寬度,這也是為了美觀而設定的固定值,如果圖片載入完成,呼叫processImageScaleInRotate方法計算寬高,該方法如下

//圖片旋轉時重新計算詳情圖片的寬高
processImageScaleInRotate:function(){
    //獲取圖片原始寬高
    let nw = this.detailImageNaturalWidth,
        nh = this.detailImageNaturalHeight;
    //根據旋轉角度來計算該圖是初始狀態還是旋轉過90度橫豎交換的情況
    let angel = this.detailRotateAngel;
    //圖片旋轉後的寬高
    let imageRotatedWidth,imageRotatedHeight;
    let clientWidth = this.$refs.wrapper.clientWidth;
    let ratio = nh / nw;
    //是否是初始狀態
    let isInitialState = true;
    if(angel === 90 || angel === 270 || angel === -90 || angel === -270){
      isInitialState = false;
    	//由初始狀態旋轉一次的情況
    	if(nh > clientWidth){
        imageRotatedWidth = clientWidth;
        imageRotatedHeight = imageRotatedWidth / ratio;
      }else{
        imageRotatedWidth = nh;
        imageRotatedHeight = imageRotatedWidth / ratio;
      }
    }else{
      //旋轉一次變為初始狀態的情況
      isInitialState = true;
      if(nw > clientWidth){
        imageRotatedWidth = clientWidth;
        imageRotatedHeight = imageRotatedWidth * ratio;
      }else{
        imageRotatedWidth = nw;
        imageRotatedHeight = imageRotatedWidth * ratio;
      }
    }
    //注意這裡的判斷,width和height在旋轉狀態下容易弄反
    return {
    	width:isInitialState?imageRotatedWidth:imageRotatedHeight,
        height:isInitialState?imageRotatedHeight:imageRotatedWidth
    }
},
複製程式碼

這個方法較為複雜,我們可以發現無論怎麼旋轉,圖片的[寬,高]這一組資料只可能存在2種狀態,初始狀態和旋轉一次後的狀態,這很好理解,拿一張圖片操作一下就明白了,這2種狀態就是由圖片的旋轉角度detailRotateAngel決定,當這個角度為90,270,-90,-270度時就是旋轉一次後的狀態,否則為初始狀態,然後分別針對這2種狀態計算寬高即可

初始狀態的計算:首先獲取到圖片的原始寬高nw,nh,然後計算其比例ratio,當nw>clientWidth時,表明圖片的原始寬度大於外層div的最大寬度,這時圖片的寬度就是clientWidth,否則寬度是自己的原始寬度,高度的話根據ratio乘以寬度得出。這樣就能保證圖片的寬度不會超出div的寬度。

由初始狀態旋轉一次後的計算:原理同上,只不過這時需要判斷的值是nh(原始高度),因為圖片寬高置換了

最後return返回width和height即可,上面處理完了圖片的寬高計算,還剩一個問題就是外層div的高度計算,因為圖片絕對定位,如果外層div的高度不變的話,圖片會溢位

 <div class="detail-img-wrapper" :style="{height:outerDivHeight+`px`}">
 <div>
複製程式碼

我們給外層div動態繫結height,outerDivHeight是計算屬性

//外層div的高度(隨著圖片旋轉而變化)
outerDivHeight: function(){
    //根據旋轉角度來計算該圖是初始狀態還是旋轉過90度橫豎交換的情況
    let angel = this.detailRotateAngel;
    if(angel === 90 || angel === 270 || angel === -90 || angel === -270) {
      //由初始狀態旋轉一次的情況
    	return this.detailImageWidth
    }else{
    	//初始狀態
      return this.detailImageHeight
    }
}
複製程式碼

原理很簡單,同上,也是根據圖片的狀態來計算,至此整個旋轉的邏輯就結束了。

是否是長圖

這個邏輯其實也很簡單,程式碼如下

//計算每張圖是否是長圖
calcImageIsLongImage: function(){
    let self = this;
    //計算每張圖是否是長圖
    this.imageList.forEach((item,index)=>{
      let image = new Image();
      image.onload = function(){
        let ratio = image.naturalHeight / image.naturalWidth;
        if(ratio > self.longImageLimitRatio){
        	//通過$set方法修改陣列中的值
          self.$set(self.isLongImageList,index,true)
        }
      };
      image.src = item;
    })
},
複製程式碼

該方法在mounted中呼叫,遍歷prop傳入的圖片url陣列,然後每張圖new一個Image,在onload中獲取其寬高比,如果大於閾值則設定長圖陣列isLongImageList中的那一項為true,最終通過span標籤絕對定位於圖片div中
<span class="long-image" v-show="isLongImageList[index]">長圖</span>

全屏大圖元件

這個元件比較簡單,元件fixed定位,外層div的css如下,寬高滿屏,z-index儘量大

  position: fixed;
  left:0;
  top:0;
  width:100vw;
  height:100vh;
  z-index:10000;
複製程式碼

下圖中是一張很長的圖,長度有4個螢幕高度,初始狀態下要求整個螢幕要能夠完全顯示該圖片

用Vue實現一個掘金沸點圖片展示元件

這個怎麼做呢?只需要給img標籤設定如下css即可

.img{
  max-width: 100vw;
  max-height: 100vh;
}
複製程式碼

最大寬高都是滿屏,因為限制了最大高度,所以圖片的高度不會超出螢幕高度,不會出現滾動條,那麼現在要求點選圖片後能夠檢視原始圖片,這時候就只需要設定

.img{
    max-height: none;
}
複製程式碼

圖片沒有最大高度限制,因此圖片顯示為原始的高度,此時如果圖片高度超過螢幕高度,出現滾動條,圖片也變寬為原始的寬度

用Vue實現一個掘金沸點圖片展示元件

相關文章