Autor: Paweł Rajewski

O ile pozycjonowanie obiektów jest dosyć proste, to w pomiarach ich położenia można naprawdę utonąć. Oczywiście, trzeba korzystać ze skryptów, ale nie to jest tu głównym problemem.

Kłopotem jest mnogość właściwości i miejsc, z których można dane odczytać i spora zależność uzyskiwanych wyników od konstrukcji strony. Pomiary to gęsto zaminowany teren pełen pułapek czyhających na nieostrożnego śmiałka.

Od razu na wstępie trzeba rozróżnić dwa rodzaje „położeń obiektu”. Pierwszy to położenie w strukturze serwisu (gwoli ścisłości należałoby go rozdzielić na położenie w kodzie i w strukturze logicznej). Oczywiście nie mają tu sensu żadne współrzędne, można natomiast mówić o obiektach nadrzędnych, podrzędnych i równorzędnych (położonych „wyżej”, „niżej” i na tym samym poziomie w strukturze strony), poprzednich, następnych, o ich numerze (indeksie) w przeróżnych kolekcjach itp. Ten rodzaj położenia nie będzie tematem artykułu.

Drugi rodzaj położenia, temat artykułu, to położenie obiektu na „rysunku” strony, co dotyczy wyłącznie obiektów „widzialnych” i wiąże się z odczytywaniem ich współrzędnych. Trzeba pamiętać, że współrzędne zawsze podawane są względem czegoś i nie istnieje coś takiego jak współrzędne bezwzględne. Możemy mieć współrzędne względem widocznego obszaru monitora, względem obszaru roboczego okna, względem innego obiektu na tej samej stronie albo względem jakiegoś punktu bieżącego obiektu. Niektóre współrzędne zmieniają się przy przesuwaniu okna czy przewijaniu strony, inne nie. Właśnie ta względność stanowi największy problem i sprawia, że współrzędne różnych obiektów mogą nie być ze sobą bezpośrednio porównywalne.

Właściwości left i top

sX=obiekt.style.left; 
sY=obiekt.style.top; 

Pierwszy nasuwający się sposób odczytywania współrzędnych – i pierwsza pułapka. Z właściwości left i top często próbują korzystać osoby początkujące. Piszemy jak powyżej i… może się okazać, że nie uzyskujemy nic (łańcuch pusty). Dlaczego? Bo obiekt style zawiera tylko wartości nadane bezpośrednio przy pomocy zapisu STYLE=”…”. Jeśli element ma właściwości nadane poprzez arkusz stylów to jego obiekt style będzie pusty. Trzeba wtedy sięgnąć do obiektu currentStyle, tak jak opisałem to w artykule „Własna specyfikacja DHTML”. Jeśli jednak położenie elementu w ogóle nie było określone (wynika jedynie z automatycznego generowania strony), to w currentStyle znajdziemy nic nie mówiący łańcuch auto…

Załóżmy jednak, że udało się uzyskać jakiś wynik. Jest to jednak łańcuch, a nie liczba. Może to być np. 13px albo 3cm co wymaga dalszej obróbki aby uzyskać liczby nadające się do obliczeń. Nie wchodzą więc w grę skrótowe zapisy powiększające lub pomniejszające współrzędne w rodzaju obiekt.style.left+=5; (co miałoby przesunąć obiekt o 5px w prawo). Ponieważ left jest łańcuchem, taki zapis spowoduje błąd.

Właściwości posLeft i posTop

vX=obiekt.style.posLeft; 
vY=obiekt.style.posTop; 

Właściwości posLeft i posTop zwracają wartości left i top w postaci liczb, a nie łańcuchów, co jest już sporym ułatwieniem. Ale uwaga – jednostka nie zostaje zmieniona. posTop da więc wynik 2 zarówno dla top:2px, jak i dla top:2cm. Oczywiście, nie rozwiązuje to innych, wymienionych wcześniej problemów.

posTop i posLeft można natomiast używać do wygodnego zmieniania pozycji obiektu. Odczytujemy wartość właściwości, przetwarzamy ją (co jest łatwe, bo mamy do czynienia z liczbą), po czym podstawiamy ponownie. W ten sposób zmieniamy „liczbowo” pozycję zachowując jednostkę, w której jest wyrażona. Oto przykład:

<DIV STYLE="width:100px; height:100px; position:absolute; left:0px; 
top:0px; background:#FF0000;" 
onclick="this.style.posLeft+=5;"></DIV> 

Po każdym kliknięciu czerwonego kwadratu przesunie się on o 5px w prawo. Jak wspomniałem, ta sama funkcja z wykorzystaniem właściwości left spowodowałaby błąd. Jeśli zmienimy jednostkę left podając np. left:0cm; to po każdym kliknięciu kwadrat będzie przesuwał się o 5 centymetrów, a nie pikseli.

Właściwości pixelLeft i pixelTop

iX=obiekt.style.pixelLeft; 
iY=obiekt.style.pixelTop; 

Podobnie jak posLeft i posTop, właściwości te zwracają liczby będące odpowiednikami left i top. W tym przypadku są to jednak wartości przeliczone na piksele. Wartości te mają wszystkie wady informacji uzyskiwanych z obiektu style, a przede wszystkim niedostępność gdy left i top nie zostały podane wprost.

Problemy z obiektem style

Wspólną cechą wszystkich współrzędnych odczytywanych z obiektu style lub currentStyle jest to, że nie są one wynikiem żadnego „pomiaru”. To po prostu zwrot wartości (czasem przeliczonych), które sami podaliśmy jako left i top – bezpośrednio lub w wyniku działania skryptu. A to znaczy, że jeśli element zostanie przesunięty bez użycia obiektu style, odczytane z tego obiektu wartości nie będą odpowiadały jego aktualnej pozycji:

<H1 STYLE="position:relative;"> 
ABC 
<DIV ID="test" STYLE="position:absolute; top:2px; left:2px; 
color:#FF0000;">ABC</DIV> 
</H1> 

W tym przypadku top i left obiektu test zwrócą łańcuch 2px, a pozostałe omówione właściwości liczbę 2 bez względu na to, w którym miejscu strony znajdzie się obiekt H1, a wraz z nim obiekt test. Co gorsze, nie ma żadnej metody na stwierdzenie, że top i left określają tu położenie względem H1, a nie względem BODY.

Podobna sytuacja występuje, gdy left i top pełnią rolę wskaźników przesunięcia:

<DIV ID="test" STYLE="position:relative; top:2px; left:2px;">ABC</DIV> 

Tu także otrzymamy wartości 2px lub 2 bez żadnej informacji, że nie są to współrzędne obiektu, a jedynie dystans jego przesunięcia względem bliżej nieokreślonej pozycji „normalnej”. Po prostu – sami wpisaliśmy do obiektu style wartość 2px, to otrzymujemy te same 2px z powrotem…

I ostatnia wada – współrzędne odczytywane z obiektu style są dostępne tylko w przypadku elementów mających pozycjonowanie specjalne (relative lub absolute). Próba odczytania tym sposobem położenia zwykłego elementu zakończy się otrzymaniem łańcucha pustego lub zera (obiekt style) albo wartości auto lub undefined (obiekt currentStyle).

Jak widać, obiekt style, służący do ustalania pozycji, sprawia wiele problemów przy jej odczytywaniu.

Właściwości offsetLeft, offsetTop i offsetParent

iX=obiekt.offsetLeft; 
iY=obiekt.offsetTop; 
oP=obiekt.offsetParent; 

To jest „prawdziwy” sposób odczytywania współrzędnych, choć też nie pozbawiany pułapek. Aby zrozumieć dokładnie jak działa, trzeba wprowadzić nowe pojęcie – client area.

Każdy widoczny element strony posiada obszar określany jako client area – jakby swój obszar roboczy. Jest to obszar wewnątrz obramowań i ewentualnych pasków przewijania, a więc ten, na którym rzeczywiście może być wyświetlana zawartość elementu. Zawartość elementu może całkowicie wypełniać obszar client area – i tak dzieje się gdy wymiary elementu dopasowują się do wymiarów zawartości. Zawartość może być mniejsza niż obszar client area i wówczas część obszaru client area jest pusta. I wreszcie zawartość może być większa niż obszar client area i wtedy zwykle mamy do dyspozycji paski przewijania. Client area jest więc rodzajem „okienka”, w którym element wyświetla swoją zawartość.

W skład obszaru client area wchodzi margines wewnętrzny elementu (padding), ale nie wchodzą paski przewijania, obramowania (border) i marginesy zewnętrzne (margin). Jeśli więc obiekt nie ma ani obramowań, ani pasków, wymiary client area są takie same jak wymiary obiektu. Jeśli obramowania lub paski są obecne, wymiary client area są mniejsze niż wymiary obiektu.

Powróćmy do odczytu współrzędnych z właściwości offsetLeft i offsetTop. Praktycznie każdy obiekt posiada te właściwości, których wartości nadawane są automatycznie w czasie generowania strony. offsetLeft i offsetTop zwracają liczby całkowite będące poziomą i pionową współrzędną lewego górnego rogu obiektu, podaną w px, względem… no właśnie, względem lewego górnego rogu obszaru client area nadrzędnego obiektu odniesienia określanego jako offsetParent. Przy czym obiekt offsetParent może, choć nie musi, być tym samym obiektem, który nadaje przestrzeń współrzędnych, opisaną w poprzednim odcinku. Można zatem uzyskać z offsetTop wynik 10 i nadal nie wiedzieć czym to 10 jest… Na szczęście każdy obiekt posiada informację względem jakiego obiektu nadrzędnego określane jest jego położenie i jest to właściwość offsetParent. Ale uwaga: ta właściwość jest „skrótem” do obiektu i zwraca obiekt odniesienia, a nie np. jego nazwę.

Jako ogólną regułę można przyjąć, że dla elementów mających ustawione pozycjonowanie relative lub absolute obiektem odniesienia będzie obiekt nadający lokalną przestrzeń współrzędnych lub obiekt BODY jeśli lokalnej przestrzeni współrzędnych nie ma (uwaga na dwa tryby pracy Explorera 6 – artykuł „Internet Explorer a CSS”).

W przypadku obiektów, które nie mają przypisanego żadnego atrybutu position (a zatem są „normalnie” położone w treści strony) sytuacja bardzo się komplikuje. W takim przypadku za obiekt offsetParent przyjmowany jest najbliższy obiekt nadrzędny posiadający tzw. layout. Posiadają go elementy blokowe, elementy o pozycjonowaniu absolutnym oraz te, którym ustawiono przynajmniej jeden z wymiarów: width lub height.

Posiadanie (lub nie posiadanie) layout-u przez konkretny element można sprawdzić badając jego właściwość hasLayout. Dodając w ramach elementu:

Skasowane dane to nie zawsze tragedia - ściągnij program i odzyskaj dane
onclick="alert(this.currentStyle.hasLayout);" 

…i klikając go, dowiemy się czy posiada on layout (true) czy nie (false).

Od reguły tej bywają jednak wyjątki np. element DIV. Choć jest to element blokowy, nie posiada layoutu dopóki nie nadamy mu przynajmniej jednego z wymiarów. Trudno powiedzieć czy jest to zamierzona właściwość czy błąd przeglądarki (używam IE5.5) – nawet materiały Microsoftu pozostawiają w tej kwestii wątpliwości.

Innego rodzaju wyjątkiem jest tabelka. Jeśli nie wchodzi w grę pozycjonowanie specjalne, to dla komórki tabelki (samej komórki, a nie jej zawartości!) obiektem offsetParent będzie obiekt TABLE, a nie obiekt TR – mimo, że TR posiada layout. Jak widać, sytuacja może być naprawdę zawikłana, szczególnie gdy mamy do czynienia ze „SPAN-ami w DIV-ach w tabelkach”, z których część ma podane wymiary, a część nie. W wątpliwych sytuacjach można dodać do obiektu zapis:

onclick="alert(this.offsetParent.tagName);" 

…by po kliknięciu go dowiedzieć się względem czego określone są aktualnie jego współrzędne offsetTop i offsetLeft.

Na obiekt uznawany za offsetParent mogą mieć wpływ pozornie niewielkie zmiany na stronie. Oto przykład możliwych kłopotów:

<BODY><BR><BR><BR> 
<SPAN STYLE="width:200px";><A 
HREF="#">link</A></SPAN> 
...reszta strony 

Elementem offsetParent linku będzie element SPAN. Wystarczy jednak, aby SPAN nie miał ustawionych wymiarów i już obiektem offsetParent linku staje się obiekt BODY, co skutkuje zupełnie inną wartością zwracaną przez offsetTop.

W szczególnych sytuacjach na właściwość offsetParent może mieć wpływ nawet model przeglądarki otwierającej stronę – należy więc zawsze dbać, aby dla interesujących nas obiektów właściwość ta była jednoznacznie określona. Np. w przypadku DIV-a można nadać mu wymiar: STYLE=”width:100%;” zamiast pozostawiać go bez wymiarów. Będzie wyświetlany identycznie, a na pewno uzyska layout i tym samym nie sprawi problemów przy pomiarach współrzędnych.

Czasami aby otrzymać współrzędną obiektu względem „strony” (co zwykle oznacza względem obiektu BODY), trzeba przejść cały łańcuszek obiektów, których współrzędne należy zsumować. Np.:

iY=obiekt.offsetTop+obiekt.offsetParent.offsetTop+obiekt.offsetParent.offsetParent.offsetTop; 

Na szczęście są to sytuacje rzadkie, niemniej mogą się zdarzyć.

Uwaga: właściwości offset… to właściwości tylko do odczytu. Przy ich pomocy nie można ustawiać obiektów.

Właściwości clientLeft i clientTop

iX=obiekt.clientLeft; 
iY=obiekt.clientTop; 

Jeśli obiekt posiada obramowanie, współrzędne jego obszaru client area będą inne niż współrzędne zwracane przez offsetLeft i offsetTop (które zwracają współrzędne całego obiektu, a więc łącznie z obramowaniami). Obszar client area jest przecież przesunięty względem lewego górnego rogu obiektu o grubość lewego i górnego obramowania. Wartości tego przesunięcia (podane w px) zwracają właściwości clientLeft i clientTop. Przykład:

<DIV ID="test" STYLE="width:200px; border:10px solid #000000;">ABC</DIV> 

…właściwości offsetLeft i offsetTop zwrócą współrzędne obiektu, a właściwości clientLeft i clientTop – wartości 10. Współrzędne obszaru client area obiektu test to test.offsetLeft+test.clientLeft oraz test.offsetTop+test.clientTop.

Po co stosować te właściwości, skoro można po prostu odczytać grubość obramowań? Dla wygody. W przypadku client… mamy do czynienia z liczbami całkowitymi wyrażonymi w pikselach, podczas gdy grubości obramowań odczytane z obiektu style to łańcuchy zawierające także jednostkę (a często i inne dane), a więc trudne do dalszego przetwarzania.

Uwaga: właściwości te nie zawsze są dostępne – można z nich korzystać w przypadku elementów posiadających layout.

Pułapka! Nawet, jeśli nie ustawimy jakiejś wartości, może się zdarzyć, że Explorer zrobi to za nas. Np. gdy strona wyświetlana jest we własnym oknie, element BODY ma domyślnie ustawiane obramowanie o grubości 2px (w „starym” trybie pracy IE), a zatem document.body.clientLeft i clientTop zwracają wartość 2. Gdy ta sama strona wyświetlana jest w ramce, BODY domyślnie nie ma obramowania, za to dwupikselowe obramowanie otrzymuje element FRAMESET.

Jak widać, odczytywanie położenia obiektu bardzo zależy od konstrukcji strony – ten sam zapis na dwóch różnych stronach może dać dwa różne wyniki. Ba, drobna zmiana w budowie strony może sprawić, że prawidłowo funkcjonujący odczyt współrzędnych zacznie działać błędnie. To jeden z powodów, dla których tak trudno podawać gotowe skrypty związane z pozycjonowaniem. Nie ma żadnej gwarancji, że skrypt umieszczony na „obcej” stronie zadziała poprawnie (stąd komplikacja i rozmiary uniwersalnych „gotowców”, które powinny pracować w każdych warunkach. Skrypt napisany pod konkretną stronę i przeglądarkę jest zwykle znacznie krótszy i szybszy).

Właściwość z-index

iZ=obiekt.style.zIndex; 

Z odczytywaniem współrzędnej pionowej obiektu nie ma na szczęście większych problemów. Odczytanie właściwości zIndex da w wyniku współrzędną pionową bez względu na to czy była ona ustawiona wprost dla danego elementu czy nie. Należy jedynie pamiętać, że współrzędne wszystkich obiektów nie mających ustawionego specjalnego pozycjonowania (relative lub absolute) wynoszą 0 (zero), podobnie jak obiektów z pozycjonowaniem specjalnym, ale bez ustawionej wartości z-index. Trzeba też pamiętać, że przy zagnieżdżaniu obiektów zIndex obiektów „wewnętrznych” podawany jest w stosunku do obiektu-kontenera „zewnętrznego” (opisałem to w poprzednim odcinku). W takiej sytuacji „zetindexy” obiektów wewnątrz kontenera i na zewnątrz niego nie są ze sobą bezpośrednio porównywalne. Na koniec trzeba też wiedzieć, że elementy „okienkowe”, takie jak SELECT, a do IE5.5 także IFRAME, nie obsługują wartości z-index i zawsze rysowane są na samym szczycie stosu.

Właściwości scrollLeft i scrollTop

iX=obiekt.scrollLeft; 
iY=obiekt.scrollTop; 

W przypadku elementów posiadających zawartość przewijaną wewnątrz obszaru client area (np. BODY, DIV, P…) istnieje jeszcze jedna para współrzędnych – scrollLeft i scrollTop. Określają one dystans, na jaki zawartość obiektu została przewinięta w poziomie i w pionie. De facto są to więc współrzędne najwyższego i położonego najbardziej na lewo punktu zawartości, który jest widoczny w swoim „oknie” (w obszarze client area) – względem początku tej zawartości. Brzmi to może skomplikowanie, ale jeśli posiadamy element, którego zawartość została przewinięta o 100px w dół, to jego właściwość scrollTop wynosić będzie właśnie 100.

Jeżeli więc dla jakiegoś obiektu offsetLeft zwraca 120, clientLeft – 5, a scrollLeft – 200, oznacza to, że współrzędna lewego brzegu całego obiektu wynosi 120px, ma on lewe obramowanie o grubości 5px, czyli współrzędna lewego brzegu jego obszaru roboczego wynosi 125px, a zawartość tego obiektu jest aktualnie przewinięta o 200px w prawo (czyli poza lewą krawędzią jego obszaru roboczego znajduje się jeszcze 200px „niewidzialnej” zawartości). Proste, prawda?

Właściwości scrollLeft i scrollTop można także ustawiać i w ten sposób wymuszać przewijanie zawartości obiektów o określony dystans. Przykład:

<P STYLE="width:500px; overflow:hidden; font-size:200px;" 
onclick="this.scrollLeft+=50;"> 
Wieeelki&nbsp;tekst! 
</P> 

Po każdym kliknięciu tekstu przewinie się on o kolejne 50px. Ciekawą właściwością scrollLeft i scrollTop jest automatyczne ograniczanie wpisywanej wartości do dopuszczalnego zakresu. Jeśli podamy liczbę ujemną – zostanie przyjęta wartość zero. Jeśli podamy liczbę większą niż maksymalny dystans przewinięcia – zostanie przyjęta wartość maksymalna. Dlatego w powyższym przykładzie wartość scrollLeft nie będzie rosła do nieskończoności.

Właściwości screenLeft i screenTop

iX=window.screenLeft; 
iY=window.screenTop; 

Na koniec dysponujemy jeszcze jedną parą współrzędnych – lewego górnego rogu obszaru roboczego okna (jego client area) względem lewego górnego rogu ekranu. Dzięki temu możemy określić, w którym miejscu ekranu wyświetlana jest nasza strona i wyliczać współrzędne różnych obiektów względem ekranu. Ta właściwość jest tylko do odczytu i nie można przy jej pomocy przesuwać okna.

Dokonując ewentualnych pomiarów z użyciem screenLeft i screenTop należy pamiętać, że obszar client area okna to nie to samo co obszar client area elementu BODY (BODY posiada przecież domyślne obramowanie, a w IE6 może mieć także „prawdziwe” marginesy). Tym bardziej nie są to współrzędne całego okna, które posiada własne obramowanie, paski tytułu, menu, statusu itd.

Ponieważ screenLeft i screenTop pozwalają zorientować się, w którym miejscu ekranu znajduje się obszar roboczy okna, pozwalają też na wyliczanie przesunięć okna w taki sposób, by osiągać różne brzydkie efekty w rodzaju… i tu sam siebie ocenzuruję pozostawiając resztę domyślności Czytelników.

W kolejnym odcinku – odczytywanie wymiarów obiektów. Cdn.

Paweł Rajewski

Skasowane dane to nie zawsze tragedia - ściągnij program i odzyskaj dane