不知各位遇到特别長的圖檔時是怎麼處理的?
是 截取符合長寬的部分做臨時展示?
還是 硬要長寬100%模糊(啥也看不清)展示?
還是 先拿一個壓縮的圖檔做占位,在滑鼠移入或點選時放大預覽?
今天偶然打開PC端QQ空間時,我發現了一種似乎更好的方式 —— 滑鼠移入時在範圍内上下滾動圖檔預覽,移出時停止滾動。直到使用者點選圖檔跳轉到詳情展示:
分析
這種方式着實讓我“眼前一亮”,一定程度上帶給了使用者新奇的體驗感。順着思路,一鍵 f12 打開源碼,我看到了這樣的代碼:
顯而易見,QQ應該是采用了js監聽滑鼠位置的做法,動态改變
img
标簽中自定義屬性的值,并根據此去改變圖檔的
margin-top
值,用
transition
屬性去制造動畫效果。
模拟實作
為了友善些,這裡筆者采用兩個空标簽分割“包裹圖檔的元素”,然後分别在上面監聽滑鼠事件的做法,實作效果如下:
首先,“科普”幾個API,它們将會是你的助力:
-
:看到前面的image沒?這是用來擷取圖檔原始高度的(同系的還有image.naturalHeight
,你可以用它來确定包裹元素的最大/最小寬度);image.naturalWidth
-
:offset系的API,用來擷取dom元素和離它最近的父元素頂部的距離(同系的還有dom.offsetTop
、offsetLeft
(傳回元素的像素寬高,包含該元素的内邊距和邊框,是一個整數且不包含:before或:after等僞類元素的寬高)、offsetWidth/offsetHeight
(擷取父元素));offsetParent
-
:它有四個常用值:left、top、right、bottom,分别是相對于目前視口(即此tab網頁視窗左側、頂部、右側、底部)的位置;dom.getBoundingClientRect()
-
:scroll系的API,用來擷取元素的真實高度(同系的還有dom.scrollHeight
),一般不會用它來作用于圖檔上,因為它必須等元素加載出來才能确定;scrollWidth/scrollLeft/scrollTop
-
:inner系的API,它們隻作用在window對象上,傳回視窗的文檔顯示區的高度(同系的還有一個window.innerHeight
) <-> 相對的兩個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時間竟然是動态變化的:
這…我猜測可能是設定了一個從上到下固定的時間,然後在JS中按照滑出部分高度(已經滑動的距離)占總高度的比例動态調節時間。。。相關代碼筆者正在嘗試ing