CSS秘密花園: 交互式圖像對比
《 CSS Secrets 》是 @Lea Verou 最新著作,這本書講解了有關于CSS中一些小秘密。是一本CSSer值得一讀的一本書,經過一段時間的閱讀,我、@南北和@彥子一起將在W3cplus發布一系列相關的讀后感,與大家一起分享。
有時候我們需要向別人展示兩幅圖像的視覺差異,通常一幅是修改前的圖像、一幅是修改后的。例如,把兩幅圖像放在一起,展示照片處理的效果。比如一些美容師的網站要展示某些美容護理的效果,某個地理區域發生災難的結果。
最常見的方案是將兩張圖片并排放置。但是,這樣的話人的眼睛就只能注意到非常突出的差異、而察覺不到那些小的變化。如果對比不是那么重要或者兩圖之間的差異非常大的話,這種方法是沒有問題的,但是對于其它情況呢,我們需要一些更好的方法。
從用戶體驗的角度看,這個問題有很多解決方案。一種常見的解決方案是通過一個GIF動圖或CSS動畫在同一個位置快速連續地顯示兩張圖片。這比兩張圖片并排放要好很多。但是用戶要發現這之間的差異也比較耗時,因為用戶需要等待好幾次迭代,好讓他們的眼睛能夠把整張圖片觀察完。
一個交互式圖像對比小部件的例子,可以使用戶對比2011年倫敦暴動帶來的災難性結果,來自英國主要新聞媒體The Guardian。用戶可以拖動兩幅圖像中間的白色豎條,但是并沒有提示來告訴用戶“這是可以拖動的”,這就是為什么需要幫助文本(“Move the slider…”)了。理想情況下,一個良好的、可學習的界面是不需要幫助文本的。
一個更實用的解決方案是“image comparison slider”。它把兩個圖像疊加在一起,讓用戶拖動divisor進行選擇,是顯示這一個還是另一個。當然,這樣的控制在HTML中不是實際存在的。我們需要通過我們真實存在的元素來模擬它,多年來也一直有許多實現這個效果的示例,通常需要JavaScript框架和一大堆的JS代碼。
在一些變型中,用戶可以直接移動鼠標來代替拖動。這樣做的好處是可以更容易讓用戶注意到并去使用,但是體驗卻是相當刺激的。
有沒有什么簡單的方法可以實現這樣的一個控制?當然有,而且有倆!
CSS resize方案
如果我們仔細想想,image comparison slider通常包括一張圖像,和一個水平方向大小可調的元素,用于顯示另一張圖像。這通常就是需要JavaScript框架的地方了:讓頂部的圖像水平方向大小可調。但是,我們并不是一定需要腳本來讓元素大小可調。在CSS3中,我們有這樣一個屬性: resize 。
即使你沒有聽過這個屬性,你很可能已經使用過它,因為它在 <textarea> 中的默認值是 both ,這使得它們在各個方向都是可調整的。但是,實際上它可以被設置在任何元素上,只要它的 overflow 屬性值不是 visible 。幾乎所有元素的 resize 屬性的默認值都是 none ,這使得它們不能調整大小。除了 both ,它還接受 horizontal 和 vertical 值,用來限制哪個方向可以調整大小。
給 <textarea> 應用 resize: vertical 是一個不錯的方法,既可以禁用水平調整,又保持大小可調整,因為水平調整通常會破壞頁面布局。
這里可能有一個疑問:我們也許可以使用這個屬性來實現我們的圖像滑塊?不試試怎么知道呢!
我們的第一個想法可能是使用兩個 <img> 元素。但是,直接給 <img> 應用 resize 看起來非常丑,直接調整圖像大小的話會導致扭曲。所以,還是把它應用到一個 <div> 容器中可能比較合理。因此,我們的HTML標簽如下:
<div class="image-slider">
<div>
<img src="adamcatlace-before.jpg" alt="Before" />
</div>
<img src="adamcatlace-after.jpg" alt="After" />
</div>
一旦 object-fit 和 object-position 得到了更廣泛的瀏覽器支持,這就不成問題了,因為我們可以用控制背景圖像縮放的方式,同樣的方法來控制圖像的縮放。
然后我們需要應用一些基礎的CSS用于定位和設置尺寸:
.image-slider {
position:relative;
display: inline-block;
}
.image-slider > div {
position: absolute;
top: 0; bottom: 0; left: 0;
width: 50%; /* Initial width */
overflow: hidden; /* Make it clip the image */
}
.image-slider img { display: block; }
現在結果如圖所示,但是這仍然是靜態的。
如果我們手動改變寬度,我們可以看到用戶所有可能的調整結果。通過 resize 屬性讓寬度隨著用戶交互動態改變,我們還需要兩個聲明:
.image-slider > div {
position: absolute;
top: 0; bottom: 0; left: 0;
width: 50%;
overflow: hidden;
resize: horizontal;
}
唯一的視覺變化就是,現在大小調整句柄出現在了top圖像的右下角,
我們現在可以拖動它,把它調整到我們的主要內容!但是,拖動我們的小工具時,也發現了一些缺陷:
- 我們可以調整 <div> 的大小到超過圖像的寬度
- resize 句柄很難點中
第一個問題很容易解決。我們只需要指定 max-width 的值為 100% 。但是,第二個問題稍微復雜一點。可惜,現在還是沒有標準的方法來給resize句柄添加樣式。一些渲染引擎支持私有偽元素(如, ::-webkitresizer ),但是它們的結果非常受限制,不僅是在瀏覽器支持方面,還有樣式靈活性方面。但是,希望仍在:這說明在 resize 句柄上覆蓋一個偽元素不會干擾它的功能,甚至不需要設置 pointer-events: none 。所以,給 resize 句柄添加樣式的跨瀏覽器的解決方案就是在它的上邊覆蓋一個偽元素。如下:
.image-slider > div::before {
content: '';
position: absolute;
bottom: 0; right: 0;
width: 12px; height: 12px;
background: white;
cursor: ew-resize;
}
注意 cursor: ew-resize 這條聲明:它增加了一個額外的支持,因為它提示用戶說這塊區域可以用作一個resize句柄。但是,我們不能依賴光標改變作為我們唯一的支持,因為它們是當用戶和控件交互時才是可視的。
現在,我們的resize句柄將會變成一個白色方塊。
在這里,我們可以接著往下,把它變成我們喜歡的樣式。例如,把它變成一個距圖像邊距為 5px 的白色三角形,
CSS如下:
padding: 5px;
background:linear-gradient(-45deg, white 50%, transparent 0);
background-clip: content-box;
為了進一步改進,我們可以給兩個圖像都應用 user-select: none 。這樣,沒有抓取到resize句柄的話也不會白白導致它們被selected。總結一下,完整的代碼如下:
.image-slider {
position:relative;
display: inline-block;
}
.image-slider > div {
position: absolute;
top: 0; bottom: 0; left: 0;
width: 50%;
max-width: 100%;
overflow: hidden;
resize: horizontal;
}
.image-slider > div::before {
content: '';
position: absolute;
bottom: 0; right: 0;
width: 12px; height: 12px;
padding: 5px;
background:
linear-gradient(-45deg, white 50%, transparent 0);
background-clip: content-box;
cursor: ew-resize;
}
.image-slider img {
display: block;
user-select: none;
}
Range input解決方案
在上一節中提到的CSS resize方法運行是非常ok的,而且代碼量也非常小。但是,它有一些缺點:
- 它不是keyboard accessible(不能通過鍵盤調整resize)的
- 拖動是調整上邊的圖像的唯一方法,這對于非常大的圖像和有運動障礙的用戶而言是很麻煩的。如果能夠在用戶點擊一個點的時候,圖像就調整到那個點,可以提供一個更好的用戶體驗。
- 用戶只能從右下角對頂部圖像進行調整,這個右下角可能很那被注意到,即使我們把它變成我們之前描述的樣式。如果我們愿意使用一點腳本,我們就可以使用一個滑塊控件(HTML range input)覆蓋在圖像的頂部,來控制調整大小,就可以把這三個問題都解決了。因為我們無論如何都是要使用JS的,我們可以通過腳本添加額外的元素,我們從如下簡單的HTML標簽開始:
<div class="image-slider">
<img src="adamcatlace-before.jpg" alt="Before" />
<img src="adamcatlace-after.jpg" alt="After" />
</div>
這樣,我們的JS代碼會把它轉變如下,然后給滑塊添加一個事件,并設置div的寬度:
<div class="image-slider">
<div>
<img src="adamcatlace-before.jpg" alt="Before" />
</div>
<img src="adamcatlace-after.jpg" alt="After" />
<input type="range" />
</div>
JavaScript代碼相當簡單:
$$('.image-slider').forEach(function(slider) {
// Create the extra div and
// wrap it around the first image
var div = document.createElement('div');
var img = slider.querySelector('img');
slider.insertBefore(img, div);
div.appendChild(img);
// Create the slider
var range = document.createElement('input');
range.type = 'range';
range.oninput = function() {
div.style.width = this.value + '%';
};
slider.appendChild(range);
});
我們初始的CSS基本和上一個方案的一樣。我們只把不需要的部分刪掉:
- 我們不需要 resize 屬性
- 我們不需要 .image-slider > div::before 規則,因為我們已經不需要resizer了。
- 我們不需要 max-width ,因為滑塊可以進行控制
經過這些調整之后我們的CSS代碼如下:
.image-slider {
position:relative;
display: inline-block;
}
.image-slider > div {
position: absolute;
top: 0; bottom: 0; left: 0;
width: 50%;
overflow: hidden;
}
.image-slider img {
display: block;
user-select: none;
}
使用 input:in-range ,而不僅僅是 input ,這樣可以在當且僅當range input被支持的時候才會給range input添加樣式。然后你可以使用級聯來隱藏它,或針對舊的瀏覽器給它應用不同的樣式。
如果我們測試了這些代碼,你會發現它是可運行的,但是它看起來有點糟糕:我們的圖片下方隨意地放置了一個range input的控件。
我們需要給它應用一些CSS,讓它定位在圖片上邊,并設置寬度和容器寬度一致。
.image-slider input {
position: absolute;
left: 0;
bottom: 10px;
width: 100%;
margin: 0;
}
如下圖所示,這看起來終于比較像樣了。
還有幾個私有偽元素可以給range input添加樣式,讓它變成我們期望的樣子。包括: ::-moz-range-track , ::-ms-track , ::-webkit-slider-thumb , ::-moz-range-thumb , 和 ::-ms-thumb 。像大多數私用特性,它們的結果往往是不一致的、脆弱的、并且無法預測的,所以我建議不要使用它們,除非你真的需要。我就提醒你們一下~
但是,如果我們只是想要在視覺上將range input和控件統一,我們可以使用一個混合模式或濾鏡。混合模式 multiply 、 screen 、 luminosity 應該可以生成不錯的效果。還有, filter: contrast(4) 可以讓滑塊變成黑白,小于1的對比值可以讓它更偏灰色。可能性是無限的,沒有什么通用的最佳選擇。你甚至可以混合模式和濾鏡一起用,如下:
filter: contrast(.5);
mix-blend-mode: luminosity;
我們還可以擴展那塊用戶使用來調整大小的區域,使之成為更棒的用戶體驗(根據費茨法則)。可以減小寬度,并結合CSS transform來完成:
width: 50%;
transform: scale(2);
transform-origin: left bottom;
你可以在圖中看到兩種處理結果。
這種做法的另一個好處是——盡管只是暫時性的——目前range input有比 resize 屬性更好的瀏覽器支持。
來自: http://www.w3cplus.com/css3/css-secrets/interactive-image-comparison.html