先來介紹下 media,確切的說應該是 CSS media queries(CSS 媒體查詢),媒體查詢包含了一個媒體型別和至少一個使用如寬度、高度和顏色等媒體屬性來限制樣式表範圍的表示式。CSS3 加入的媒體查詢使得無需修改內容便可以使樣式應用於某些特定的裝置範圍。
那麼該怎麼定義 media 呢,看下面的程式碼,你肯定能猜出個大概。
<!-- link元素中的CSS媒體查詢 --> <link rel="stylesheet" media="(max-width: 800px)" href="example.css" /> <!-- 樣式表中的CSS媒體查詢 --> <style> @media (max-width: 600px) { .facet_sidebar { display: none; } } </style>
關於解釋,文件中是這麼說的,當媒體查詢為真時,相關的樣式表或樣式規則就會按照正常的級聯規則被應用。即使媒體查詢返回假, <link> 標籤上帶有媒體查詢的樣式表仍將被下載(只不過不會被應用)。
所以呢,這也是一種弊端,如果說對某個頁面定義了多個樣式標準來因對不同的 media 屬性的話,那在頁面的載入時間將會受到影響,但是話有說回來,在當前網路快速發展的時代,網速也在不斷地完善和提高,因此影響並不大,幾乎可以忽略不計。
media 還可以通過邏輯操作符(and、not、only 等)來組成 media 表示式,書寫更復雜的過濾條件,這些表示式我就不再這邊一一說明了,想深入瞭解的同學,可以閱讀相關的說明文件:https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries 這裡面有做詳細的介紹。
接下來我們來用幾個 Demo 來演示下 media 的用法及表現。
既然我們今天的目的是探討如何監聽 devicePixelRatio 屬性的變化,那麼我們就以在不同的 devicePixelRatio 值情況下,來改變某個 div 的 background 樣式,具體的程式碼如下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <style media="screen"> @media screen and (min-resolution: 2dppx) { #image { background : red; } } @media screen and (min-resolution: 1dppx) { #image { background: #000; } } </style> </head> <body> <div id="image" style="width:100px; height:100px"></div> </body> </html>
程式碼有了,那麼要怎麼測試呢?在一般情況下,devicePixelRatio 屬相是不會變化的,但是肯定會存在特殊情況的,就比如說,你的電腦接了兩個顯示器,而且兩臺瀏覽器的 devicePixelRatio 屬性是不一樣的,那麼恭喜你,你已經具備測試條件,只需要將頁面從一個屏拖到另外一個屏,這樣你就可以看到效果了。
有去測試的同學會發現,div 的背景色並沒有想程式碼中設定的那樣,在不同的 devicePixelRatio 屬性值下,展現出不同的顏色,這是為什麼呢?
這程式碼是我最開始寫程式碼,執行後發現沒效果,起初我也不知道原因,在跨屏拖動頁面的時候,在瀏覽器控制檯中,我找到了原因。那麼到底是什麼原因導致設定無效的呢?我們來看看兩個螢幕下的 Style 內容截圖,左邊是 min-resolution 等於 1,右邊是等於 2
對比著兩個圖,可以發現,在 min-resolution 等於 2 的情況下,在裡面定義的屬性被覆蓋掉了,並沒有生效,這是為什麼呢?
要解釋的話,這裡恐怕需要補充一點知識,就是關於 min- 和 max- 的字首,在程式碼中的所起到的具體效果,在文件中是這麼描述的:大多數媒體屬性帶有 “min-” 和 “max-” 字首,用於表達 “大於等於” 和 “小於等於”。這避免了使用與HTML和XML衝突的 “<” 和 “>” 字元。如果你未向媒體屬性指定一個值,並且該特性的實際值不為零,則該表示式被解析為真。如果瀏覽器執行的裝置上沒有該屬性值,包含這個屬性值的表示式一般返回假。
其實上面的說明已經幫我解釋清楚了,我再通俗地和大家解釋一下:當 devicePixelRatio 為 1 時,只有 min-resolution: 1dppx 這個條件滿足,因此 div 的顏色是黑色沒錯;當 devicePixelRatio 為 2 時,兩個 media 都滿足條件,同時 CSS 的規則是後載入的樣式將會覆蓋先載入的樣式,由於我麼將 min-resolution: 1dppx 的 media 寫在後面,因此如果兩個 media 都滿足條件的話, min-resolution: 1dppx 的 media 將會覆蓋 min-resolution: 2dppx 的 media,因此不管你把頁面拖到那個螢幕,那個 div 的背景色都是黑色。
那麼我們將兩個 media 調換一下位置,問題就順利地解決了。
<style media="screen"> @media screen and (min-resolution: 1dppx) { #image { background: #000; } } @media screen and (min-resolution: 2dppx) { #image { background : red; } } </style>
以上是根據不同的 media 條件設定不同的樣式,這是 CSS 的做法,在 JavaScript 中,沒有專門的方法來監聽 window.devicePixelRatio 屬性變化,那麼該怎麼監聽 devicePixelRatio 屬性的變化呢?方法也很簡單,看看下面的程式碼,你一定就明白了:
window.matchMedia('screen and (min-resolution: 2dppx)').addListener(function(e) {
console.info(e, window.devicePixelRatio);
});
稍微解釋下,通過 window.matchMedia(‘media expression’) 方法獲取到對應的 media,然後通過 addListener(function(e) {}) 來監聽 media 的變化。
有玩過 Canvas 的朋友一定知道,要想繪製出來的內容效果最佳的話,Canvas 自身的 width 和 height 屬性值與 style 中的 width 和 height 的比例應該恰好等於 devicePixelRatio 的值,所有如果你在切換不同 devicePixelRatio 屬性值的螢幕時,沒有重新設定 Canvas 的寬高的話,繪製出來的畫面將不是最佳的效果。
接下來我們基於 HT for Web 的 3D 模型來做一個小實驗。實驗的內容是這樣的,在 GraphView 中有一輛車根據某條路線前行,當拖到另外一個螢幕的時候,換輛車子。先來看看效果圖:
上面兩張圖分別是在不同的螢幕中的截圖,車子動起來的效果可以訪問以下連結:
實驗的地址是:http://www.hightopo.com/demo/media/index.html 以下是實驗的具體程式碼:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>HT for Web</title> <style media="screen"> @media screen and (min-resolution: 2dppx) {} html, body { padding: 0px; margin: 0px; } </style> <script src="../../oldhtforweb/lib/core/ht.js"></script> <script src="../../oldhtforweb/lib/plugin/ht-modeling.js"></script> <script src="../../oldhtforweb/lib/plugin/ht-obj.js"></script> <script> ht.Default.setImage('road', './images/road.jpg'); var init = function() { g3d = new ht.graph3d.Graph3dView(); var dm = g3d.dm(); g3d.addToDOM(); g3d.setEye(1200, 300, 0); g3d.getNote = function(data) { if (data.getTag() !== 'carNode') return null; return 'DevicePixelRatio : ' + window.devicePixelRatio; }; var carIndex = 0; window.matchMedia('screen and (min-resolution: 2dppx)').addListener(function() { carIndex = (carIndex + 1) % 2; var obj = result[carIndex]; carNode.s('shape3d', obj.name); ht.Default.setDevicePixelRatio(); }); var polyline = createPath(dm, 300), params = { delay: 0, duration: 10000, easing: function(t){ return (t *= 2) < 1 ? 0.5 * t * t : 0.5 * (1 - (--t) * (t - 2)); }, action: function(v, t){ var length = g3d.getLineLength(polyline); var offset = g3d.getLineOffset(polyline, length * v), point = offset.point, px = point.x, py = point.y, pz = point.z, tangent = offset.tangent, tx = tangent.x, ty = tangent.y, tz = tangent.z; carNode.p3(px, py - 9, pz); carNode.lookAt([px + tx, py + ty - 9, pz + tz], 'front'); }, finishFunc: function(){ ht.Default.startAnim(params); } }, carList = [ 'fordFocus', 'concept-sedan-01v2'], result = [], carNode = new ht.Node(); carNode.setTag('carNode'); carList.forEach(function(name, index) { ht.Default.loadObj('./objs/'+name+'/'+name+'.obj', './objs/'+name+'/'+name+'.mtl', { cube: true, center: true, shape3d: name, finishFunc: function(modelMap, array, rawS3) { var k = 110 / rawS3[0]; rawS3 = rawS3.map(function(v) { return v * k; }); result[index] = { 'name' : name, 'modelMap' : modelMap, 'array' : array, 'rawS3' : rawS3 }; if (index === 0) { var node = carNode; node.s({ 'wf.width' : 0, 'shape3d' : name, 'note.position' : 44, 'note' : 'DevicePixelRatio : ' + window.devicePixelRatio, 'note.face' : 'top', 'note.autorotate' : true, 'note.font' : '46px arial, sans-serif' }); node.s3(rawS3); node.r3(0, Math.PI, 0); dm.add(node); polyline.setElevation(rawS3[1] * 0.5 + 2); ht.Default.startAnim(params); } } }); }); }; var createPath = function(dm, radius) { var polyline = new ht.Polyline(); polyline.setThickness(2); polyline.s({ 'shape.border.pattern': [16, 16], 'shape.border.color': 'rgba(0, 0, 0, 0)', 'shape3d.resolution': 300, '3d.selectable': false }); dm.add(polyline); var cx = 0, cy = radius * Math.PI * 0.5, count = 500, points = [{ x: radius, y: -cy, e: 0 }], segments = [1]; for (var k = 0; k < count + 1; k++) { var angle = k * Math.PI / count; points.push({ x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle), e: 0 }); segments.push(2); } cy *= -1; radius *= -1; for (var k = 0; k < count + 1; k++) { var angle = k * Math.PI / count; points.push({ x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle), e: 0 }); segments.push(2); } polyline.setPoints(points); polyline.setSegments(segments); var shape = new ht.Shape(); shape.setPoints(points); shape.setSegments(segments); shape.s({ 'top.visible' : false, 'bottom.image' : 'road', 'bottom.reverse.flip' : true, 'bottom.uv.scale' : [13, 1], 'back.visible' : false, 'front.reverse.flip' : true, '3d.selectable': false }); shape.setThickness(180); shape.setTall(15); shape.setClosePath(true); dm.add(shape); return polyline; }; </script> </head> <body onload="init();"> </body> </html>
來介紹下這次 Demo 中都用到的了 HT for Web 的那些技術。
首先是車子,車子並不是通過 HT for Web 生成的,而是通過專業的 3D 工具設計,然後匯出 obj 和 mtl 檔案,HT for Web 對 obj 和 mtl 檔案進行解析,然後顯示在 Graph3dView 中,更多具體的介紹可以查閱我麼的 obj 文件:http://www.hightopo.com/guide/guide/plugin/obj/ht-obj-guide.html
在 obj 文件中,你會看到一個一個飛機的例子,飛機沿著設定好的路線飛行,你應該會想,這個尋路是怎麼實現的呢?其實很簡單,我們將路線切割成一個個很小很小的單元,然後根據演算法依次獲取到小單元的座標設定到移動的物體上,這樣物體就動起來了。
在 Demo 中,有一條很精緻的馬路,這條馬路就是一個 Shape 節點,根據車的路徑生成的馬路,Shape 是一個六面體,因為首尾相連了,所以沒有左右面,在這個例子中,我將馬路的 back 和 top 面隱藏了,然後 bottom 面支援翻轉,讓 bottom 面的貼圖顯示在內表面上,這樣馬路就建成了。