存取元素位置


CSS 的 position 屬性可以設定不同的值,代表元素的定位方式,如果沒有設定,大多數元素預設是 static,元素依序繪製在文件的流動版面。還可以設定的值有 relativeabsolutefixedinherit

元素的 top 表示距原點的垂直距離,正值為往下,left 表示距原點的水平距離,正值為往右。bottom 表示距原點的垂直距離,正值為往上,right 表示距原點的水平距離,正值為往左。

absolute 表示絕對定位,可以藉由設置元素 topleftbottomright 來定位,雖說名為 absolute,然而定位方式實際上是相對於元素的首個(最接近的)非 static 定位(非流動)父元素(absolute 的意義應該是指,元素不參與流動配置,瀏覽器就算改變大小,元素位置看起來都像是絕對位置,也就是不會隨著瀏覽器大小改變而流動變化)。

在絕對定位時,元素可以堆疊,此時可利用 z-index 來設定重疊順序,數值越大,表示可以堆壘在越上面,z-index 預設是頁面中標籤定義的順序,越後定義的標籤 z-index 越大。

下面這個範例運用了絕對定位、topleftz-index 屬性,可以使用滑鼠點選改變 idmessage1<div> 的位置,可以按下某個 <div>,使之 z-index 提高,就範例的 HTML 本身來說,<div> 元素的第一個非流動父元素就是 <html>,因此絕對位置都是指相對於 <html> 文件:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">

    <style type="text/css">
        #message1 {
            color: #ffffff;
            background-color: #ff0000;
            border-width: 10px;
            border-color: black;
            border-style: solid;
            width: 100px;
            height: 50px;
            padding: 50px;
            margin: 10px;
            position: absolute;
            top: 50px;
            left: 50px;
            z-index: 0;                
        }

        #message2 {
            color: #ffffff;
            background-color: #ff0000;
            border-width: 10px;
            border-color: black;
            border-style: solid;
            width: 100px;
            height: 50px;
            padding: 50px;
            margin: 10px;
            position: absolute;
            top: 150px;
            left: 150px;
            z-index: 0;                
        }
    </style>
</head>
<body>

    <div id="message1">這是訊息一</div>
    <div id="message2">這是訊息二</div>

<script type="text/javascript">
    let message1 = document.getElementById('message1');
    let message2 = document.getElementById('message2');

    document.onclick = function(evt) {
        message1.style.left = `${evt.clientX}px`;
        message1.style.top = `${evt.clientY}px`;
    };

    message1.onclick = function(evt) {
        message1.style.zIndex = 1;
        message2.style.zIndex = 0;
        evt.stopPropagation();
    };

    message2.onclick = function(evt) {
        message2.style.zIndex = 1;
        message1.style.zIndex = 0;
        evt.stopPropagation();
    };

</script>   

</body>
</html>

按此觀看結果

relative 是指元素基本上仍是流動方式,然而最後會依指定的相對值偏移,相對值是使用 topleftbottomright 設定。例如,若某元素本來流動方式擺放的位置為 100x50,若設定該元素的 positionrelative,而 topleft 分別設為 20、30 的話,則元素最後的位置就是在 (100+20)x(50+30)= 120x80 的位置。

fixed 則是以瀏覽器視埠(view-port)為基準點。例如,想要讓某個元素在捲動後,依舊可以在可視畫面中的 100x50 的位置,則可以將 position 設為 fixed,此時 topleftbottomright 就是相對於可視畫面。

想使用 JavaScript 來取得元素的確實位置,必須知道 offsetParent 為何,offsetParent 並非元素的直接父節點,而是在元素的所有父階層中,第一個可用來作為位置參考的節點。

每個元素都可以取得 offsetTopoffsetLeft,分別代表距 offsetParent 外邊框左上角的距離,所以,在簡單的排版下,想要知道元素在版面中的確實位置,基本上可以如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">

    <style type="text/css">
        #container {
            color: #ffffff;
            background-color: #ff0000;
            border-width: 10px;
            border-color: black;
            border-style: solid;
            width: 100px;
            height: 50px;
            padding: 50px;
            position: absolute;
            top: 50px;
            left: 50px;
        }

        #message {
            position: static;
            top: 20px;
            left: 20px;
        }
    </style>  
</head>
<body>

    <div id="container"><span id="message">這是一段訊息<span></div>
    <span id="console"></span>  

<script type="text/javascript">
    function offset(elem) {
        let x = 0;
        let y = 0;
        while(elem) {
            x += elem.offsetLeft;
            y += elem.offsetTop;
            elem = elem.offsetParent;
        }

        return { 
            x, 
            y, 
            toString() {
                return `(${this.x}, ${this.y})`;
            }
        };
    }

    document.onclick = function(evt) {
        let container = document.getElementById('container');
        container.style.left = `${evt.clientX}px`
        container.style.top = `${evt.clientY}px`;
        let message = document.getElementById('message');
        let console = document.getElementById('console');
        console.innerHTML = offset(message);
    };                
</script>       

</body>
</html>

按此觀看結果

一個需要元素確實位置的例子,就是像搜尋框中出現的提示,你需要知道搜尋框的位置,將傳回的關鍵字建議顯示在搜尋框下方。下面這個例子是個簡單示範:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <style type="text/css">
         #container {
             color: #ffffff;
             background-color: #ff0000;
             height: 50px;
             position: absolute;
             top: -100px;
             left:-100px;
         }
    </style>
</head>
<body>

    <div id="container">這是一段訊息</div>
    <hr>
    搜尋:<input id="search" type="text">

<script type="text/javascript">
    function offset(elem) {
        let x = 0;
        let y = 0;
        while(elem) {
            x += elem.offsetLeft;
            y += elem.offsetTop;
            elem = elem.offsetParent;
        }

        return { 
            x, 
            y, 
            toString() {
                return `(${this.x}, ${this.y})`;
            }
        };
    }

    let input = document.getElementById('search');
    let search = offset(input);
    let container = document.getElementById('container');
    container.style.left = `${search.x}px`;
    container.style.top = `${search.y + input.offsetHeight}px`;
    container.style.width = `${input.offsetWidth}px`;
</script>

</body>
</html>

按我觀看結果

(如果想做搜尋框的自動提示清單,如果能使用 HTML5,透過 <datalist> 會更方便。)

在更複雜的排版下,取得元素精確位置所要考慮的事情會更複雜,例如,CSS 的 overflow 設定可建立捲軸,偏移量不會考慮捲軸位置,如果使用了 overflow 設定,必須再考慮捲軸值。例如:

function offset(elem) {
    let x = 0;
    let y = 0;
    for(let e = elem; e; e = e.offsetParent) {
        x += e.offsetLeft;
        y += e.offsetTop;
    }

    //  修正捲軸區域的量
    for(let e = elem.parentNode; e && e != document.body; e = e.parentNode) {
        if(e.scrollLeft) {
            x -= e.scrollLeft;
        }
        if(e.scrollTop) {
            y -= e.scrollTop;
        }
    }

    return { 
        x, 
        y, 
        toString() {
            return `(${this.x}, ${this.y})`;
        }
    };
}