天天看點

QQ空間對圖檔的處理之仿QQ長圖預覽

不知各位遇到特别長的圖檔時是怎麼處理的?

是 截取符合長寬的部分做臨時展示?

還是 硬要長寬100%模糊(啥也看不清)展示?

還是 先拿一個壓縮的圖檔做占位,在滑鼠移入或點選時放大預覽?

今天偶然打開PC端QQ空間時,我發現了一種似乎更好的方式 —— 滑鼠移入時在範圍内上下滾動圖檔預覽,移出時停止滾動。直到使用者點選圖檔跳轉到詳情展示:

QQ空間對圖檔的處理之仿QQ長圖預覽

分析

這種方式着實讓我“眼前一亮”,一定程度上帶給了使用者新奇的體驗感。順着思路,一鍵 f12 打開源碼,我看到了這樣的代碼:

QQ空間對圖檔的處理之仿QQ長圖預覽
QQ空間對圖檔的處理之仿QQ長圖預覽

顯而易見,QQ應該是采用了js監聽滑鼠位置的做法,動态改變 ​

​img​

​​ 标簽中自定義屬性的值,并根據此去改變圖檔的 ​

​margin-top​

​​ 值,用 ​

​transition​

​ 屬性去制造動畫效果。

模拟實作

為了友善些,這裡筆者采用兩個空标簽分割“包裹圖檔的元素”,然後分别在上面監聽滑鼠事件的做法,實作效果如下:

QQ空間對圖檔的處理之仿QQ長圖預覽

首先,“科普”幾個API,它們将會是你的助力:

  1. ​image.naturalHeight​

    ​​ :看到前面的image沒?這是用來擷取圖檔原始高度的(同系的還有​

    ​image.naturalWidth​

    ​ ,你可以用它來确定包裹元素的最大/最小寬度);
  2. ​dom.offsetTop​

    ​​ :offset系的API,用來擷取dom元素和離它最近的父元素頂部的距離(同系的還有​

    ​offsetLeft​

    ​​ 、​

    ​offsetWidth/offsetHeight​

    ​​ (傳回元素的像素寬高,包含該元素的内邊距和邊框,是一個整數且不包含:before或:after等僞類元素的寬高)、​

    ​offsetParent​

    ​ (擷取父元素));
  3. ​dom.getBoundingClientRect()​

    ​ :它有四個常用值:left、top、right、bottom,分别是相對于目前視口(即此tab網頁視窗左側、頂部、右側、底部)的位置;
  4. ​dom.scrollHeight​

    ​​ :scroll系的API,用來擷取元素的真實高度(同系的還有​

    ​scrollWidth/scrollLeft/scrollTop​

    ​ ),一般不會用它來作用于圖檔上,因為它必須等元素加載出來才能确定;
  5. ​window.innerHeight​

    ​​ :inner系的API,它們隻作用在window對象上,傳回視窗的文檔顯示區的高度(同系的還有一個​

    ​window.innerWidth​

    ​​ ) <-> 相對的兩個​

    ​outerWidth​

    ​​ 和​

    ​outerHeight​

    ​ ,用于擷取加上工具條與滾動條視窗的寬度與高度;
順便說一句,像 ​

​img.getBoundingClientRect().top​

​​ 、​

​img.offsetTop​

​ 這些都是 隻讀 值,是以不要妄想用它們來改變元素位置!

布局如下:

<div class="box">
  <i class="before"></i>
  <img src="img/nan.png" class="img" />
  <i class="after"></i>
</div>      

這裡class為before和after的兩個标簽就是前面所說的“占位”元素(至于QQ是怎麼實作的,等筆者稍作研究後再回來更新),它們負責判斷“圖檔是應該向上滑還是向下滑”!

本來這裡筆者想采用僞元素的方式:用 ​

​::before​

​​ 和 ​

​::after​

​​ 占位并觸發事件,但是在查遍資料以後我突然想到一件事:不是經常說僞元素的優勢是脫離文檔流嗎?那還如何能夠擷取到?

唉,大意了,,,

html,body{
  margin: 0;
  padding: 0;
}

.box{
  width: 400px;
  height: 200px;
  overflow: hidden;
  position: relative;
}
.lang::after{
  content: "長圖";
  position: absolute;
  right: 0;
  bottom: 0;
  padding: 2px 3px;
  background-color: rgba(0,0,0,.36);
  color: white;
}
i{
  position: absolute;
  width: 100%;
  height: 50%;
  left: 0;
}
i.before{
  top: 0;
}
i.after{
  bottom: 0;
}
.img{
  margin-top: 0;
  transition: all 2s linear;
}      

對img元素設定一個初始的margin-top,就是為了配合下面的transition使得在js中改變top值時能夠有動畫效果!

有了上面的布局方式和API解讀,其實js實作就非常簡單了 —— 根據上面分析的按部就班來就行:

let box=document.querySelector('.box');
let img=document.querySelector('.img');
let i_before=document.querySelector('i.before');
let i_after=document.querySelector('i.after');
let box_height=box.offsetHeight;
let img_height=img.naturalHeight;
// 隻有圖檔高度大于盒子高度時才有下面的事件
if(img_height>box_height){
  console.log(1)
  box.classList.add('lang');
  let img_top=0;
  // 滑鼠移入下半部分時圖檔向下滑動
  i_after.addEventListener('mouseenter',(e)=>{
    console.log(img.offsetTop)
    img.style.marginTop=-(img_height-box_height)+'px';
  },false)
  i_after.addEventListener('mouseout',(e)=>{
    console.log(img.offsetTop)
    img_top=img.offsetTop;
    img.style.marginTop=img.offsetTop+'px';
  },false)
  // 滑鼠移入上半部分時圖檔向上滑動
  i_before.addEventListener('mouseenter',(e)=>{
    if(img_top){
      img.style.marginTop=0;
    }
  },false)
  i_before.addEventListener('mouseout',(e)=>{
    if(img_top){
      img.style.marginTop=img.offsetTop+'px';
    }
  },false)
}      

至此,效果就全部實作了。

但是如果你仔細看,你會發現由于transition動畫效果的時間是固定的,在向上/下滑動過短的情況下再向下/上滑動那麼滑動的會特别慢!

!當然,我們可以改變政策,讓圖檔的 ​

​margin-top​

​​ 不斷​

​--​

​​或​

​++​

​ 直到臨界值,但這樣勢必會帶來巨大的性能開銷。

再回到PC端QQ空間 —— 我們發現,它的transition時間竟然是動态變化的:

QQ空間對圖檔的處理之仿QQ長圖預覽

這…我猜測可能是設定了一個從上到下固定的時間,然後在JS中按照滑出部分高度(已經滑動的距離)占總高度的比例動态調節時間。。。相關代碼筆者正在嘗試ing

繼續閱讀