Perfection or Vanity

Project: Terminated

Blog nie jest już dalej prowadzony ani aktualizowany. Mimo tego, wpisy i komentarze są dalej dostępne. Możesz przeczytać pożegnalny wpis albo przejść do archiwum.

W poprzednim poście opisałem expression() w teoretyczny sposób, skupiając się na składni, zasadach i ograniczeniach. Dzisiaj chciałbym pokazać jak niesamowicie przydatne są te małe kawałki kodu - na przykładzie poprawek paru najbardziej frustrujących błędów i braków implementacji CSS Explorera.

Wstęp

Zanim przejdę do meritum, chciałbym zaznaczyć, że napisany przeze mnie kod działa w trybie zgodności ze standardami - co oznacza IE6 / IE7 z odpowiednim DOCTYPE (więcej na ten temat). IE5 i IE6 w quirksmode umieją expressions, ale należy korzystać z innych metod DOM i uważać na większe ograniczenia CSS (np.: border box model).

Można też utyskiwać, że expressions nie są ostatecznym rozwiązaniem błędów Explorera, bo wymagają włączonego JavaScript do działania. Moja odpowiedź jest bardzo prosta - jeśli użytkownik wyłącza sobie JS to musi wiedzieć co robi. Przeważająca większość szaraczków tego nie robi i aby serwować im ładne i lekkie strony należy jakoś poprawić zachowanie ich zabugowanej przeglądarki. Quid pro quo.

First-child

:first-child
  1. -ie-xp: expression(
  2. this.parsed ? 0 : (
  3. this.className = (this === this.parentNode.firstChild) ? "first-child" : "",
  4. this.parsed = 1
  5. )
  6. );

Przykład wykorzystania. Obejrzyj źródło, aby przejrzeć wykorzystany kod.

W tym kodzie wykorzystujemy operator wyrażenia warunkowego, aby sprawdzić czy obiekt this jest taki sam jak firstChild rodzica. Jeśli tak to dodajemy mu klasę first-child, którą możemy wykorzystać później w grupowaniu selektorów i aplikowaniu CSS.

Update: Poprawka dodająca klasę: this.className += …… " first-child".

Cudzysłowy w q

q

Internet Explorer ma niepełną obsługę elementu q odpowiadającego za cytat wtrącany - nie pojawiają się żadne cudzysłowy, więc nie można stwierdzić bez patrzenia w kod czy ktoś kogoś cytuje.

Podany niżej kod wstawi cudzysłowy angielskie, które nota bene są domyślne dla większości nowoczesnych przeglądarek, także tych zlokalizowanych. Istnieje nawet kod pomagający uzyskać odpowiednie znaki po zagnieżdżeniu tych elementów.

  1. q {
  2. -ie-xp: expression(
  3. this.parse ? 0 : (
  4. (bugFix = this.insertAdjacentText("afterBegin", "\201C")),
  5. this.insertAdjacentText("beforeEnd", "\201D"),
  6. this.parse = 1
  7. )
  8. );
  9. }
  10. q q {
  11. -ie-xp: expression(
  12. this.parseNested ? 0 : (
  13. (bugFix = this.insertAdjacentText("afterBegin", "\2018")),
  14. this.insertAdjacentText("beforeEnd", "\2019"),
  15. this.parseNested = 1
  16. )
  17. );
  18. }

Przykład wykorzystania.

Wykorzystujemy tutaj metodę insertAdjacentText - z parametrem afterBegin wstawiamy tekst w elemencie przed wszystkim innym a z parametrem beforeEnd za wszystkim w środku. Jest to taka pseudo generowana treść, ale operująca tylko na tekście. Nie mamy możliwości stylowania wstawionego kawałka. Zmienna bugFix pomaga zwalczyć błąd expression (nawet tu są błędy, wszakże to jest IE).

Generowana zawartość - ::before

::before

Zawsze mnie zastanawiało, czy separatory, używane przykładowo w linkach w stopce są zawartością czy tylko prezentacją. Można je wstawiać jako obrazki teł, ale pomyślałem, że można też to zrobić via GC. No bo czy nie denerwuje Was "<span>|</span>" na końcu każdego li?

Przykład wykorzystania.

  1. -ie-xp: expression(
  2. this.parse ? 0 : (
  3. isFirst = (this === this.parentNode.firstChild) ? 0 : (
  4. separator = document.createElement('span'),
  5. textnode = document.createTextNode('\2022'),
  6. separator.appendChild(textnode),
  7. separator.className = "before",
  8. this.insertBefore(separator, this.firstChild),
  9. this.parse = 1
  10. )
  11. )
  12. );

Wykorzystujemy więc sposób na first-child, aby pominąć pierwszy element (chcemy tylko punktory między linkami), tworzymy element, nadajemy mu klasę (grupowanie z prawdziwym GC) i wstawiamy przez insertBefore przed pierwszy element w li (this.firstChild).

Wypozycjonowanie w stałym miejscu ekranu

position:fixed

Aby umieścić coś na stronie za pomocą position: fixed w IE6 należy skorzystać z position: absolute i hacka podstawiającego stale aktualizowane wartości do top.

  1. body { background: url(hack) fixed; }
  2. #above, #below { position: absolute; }
  3. #above {
  4. top: expression(
  5. offset = 0 + parseInt(document.body.currentStyle.paddingTop) + parseInt(document.body.currentStyle.marginTop),
  6. document.documentElement.scrollTop + offset + 'px'
  7. )
  8. }
  9. #below {
  10. top: expression(
  11. offset = 0 + parseInt(document.body.currentStyle.paddingBottom) + parseInt(document.body.currentStyle.marginBottom),
  12. document.documentElement.clientHeight - this.offsetHeight - offset + document.documentElement.scrollTop + 'px'
  13. );
  14. }

Przykład wykorzystania.

W tym fragmencie kodu dodałem offset, który sprawdza, czy body ma jakiś margines bądź padding i jeśli tak odsuwa element o taką wysokość od krawędzi ekranu. Tak się to właśnie dzieje, gdy podamy top: 0 w kodzie zgodnym ze standardami.

Kod równoznaczny z bottom: 0 to po prostu wysokość okna minus wysokość pozycjonowanego elementu minus wspomniany offset. Całość nie drga podczas scrollowania dzięki ustawieniu background-attachment: fixed na body. Ja podałem jak zawsze fake'owy ciąg znaków, ale możecie tam dać jakiś przezroczysty GIF.

Min/max-width szerokość layoutu - revisited

min-width & max-width

O sposobie na IE6 i max-width już pisałem, ale teraz rozszerzyłem podany kod, aby uwzględniał także min-width oraz nie sypał się, gdy ustawimy padding / obramowanie na body albo nasz layout.

  1. -ie-xp: expression(
  2. this.style.borderWidth = (this.currentStyle.borderWidth == "medium") ? "0" : this.style.borderWidth,
  3. bodyInnerWidth = document.body.clientWidth -
  4. parseInt(document.body.currentStyle.paddingLeft) -
  5. parseInt(document.body.currentStyle.paddingRight) -
  6. parseInt(document.body.currentStyle.marginLeft) -
  7. parseInt(document.body.currentStyle.marginRight) -
  8. parseInt(this.currentStyle.paddingLeft) -
  9. parseInt(this.currentStyle.paddingRight) -
  10. parseInt(this.currentStyle.borderLeftWidth) -
  11. parseInt(this.currentStyle.borderRightWidth),
  12. this.style.width = (bodyInnerWidth < 401) ? "400px" : (bodyInnerWidth > 701 ? "700px" : "auto" )
  13. )

Przykład wykorzystania.

Pierwsze linijki wykonują sprawdzenie wspomnianych przeszkadzajek layoutu - obramowanie, marginesy i dopełnienia. Później następuje sprawdzenie szerokości ekranu i przypisanie na sztywno określonych wartości. Pierwsza wartość to min-width, następna max-width. Warunek zawsze musi zawierać wartość o jeden większą od żądanej (400px - w warunku musi być 401), inaczej IE polegnie.


Gdzie szukać więcej informacji:

  • MSDN - skarbnica wiedzy na temat CSS i DOM używanego w Explorerze - pełnego niestandardowych rozszerzeń, które przydają się podczas walki z błędami.

  • IE7 - biblioteka JavaScript niwelująca przepaść między IE a resztą świata, dewelopowana przez Deana Edwardsa - jej działanie opiera się na dodawaniu expressions za pomocą setExpression() - metody nie poruszanej przeze mnie ze względu na preferencje trzymania wszystkiego w plikach CSS. Mimo, że nigdy jej nie użyłem, to niewątpilwie można znaleźć tam dużo hacków na dziwactwa IE.

  • How to Create - autor strony napisał serię artykułów o tym jak zabrać się do poprawek IE. Uwzględnia nawet najstarsze wersje przeglądarek, więc tam można się udać po pomoc odnośnie IE5.

Informacje i hiperłącza

Blog o projektowaniu zgodnych ze standardami stron internetowych.

Praktyczne przykłady, sztuczki CSS, sposoby obchodzenia błędów przeglądarek, lekki i nieinwazyjny JavaScript, użyteczny design, dostępność i skrypty użytkownika.

RSS

Informacje o wpisie

Napisał riddle 14 stycznia 2007 o 22:01

Kategorie: CSS, Internet Explorer, JavaScript & DOM

Dodaj do:

Wpisy archiwalne

Archiwum miesięczne

Dzięki!

Dodaj bloga do Technorati Favorites Dodaj bloga do Del.icio.us Blog należy do sieci 10przykazań.com

  1. Shaitan [D4] 1 14 stycznia 2007, 22:05

    Niezłe, dzięki ;)

  2. Przy first-child zmieniłbym this.className = (this === this.parentNode.firstChild) ? "first-child" : "", na wersję dodającą klasę, zamiast zmieniania już obecnej, czy też jeszcze gorzej, kasowania jej.

  3. Shaitan [D4]: Nie ma sprawy ;)

    MySZ: Ach, racja – dobra uwaga.

  4. Bardzo dobre uzupełnienie poprzedniego posta

  5. Imo expressions mają sens tylko w przypadku ostatnich dwóch zastosowań, czyli wtedy, gdy potrzebna będzie reakcja w razie przerysowania strony/elementu. Pierwsze trzy powinny być obsłużone przez behaviour.

  6. firstChild zwróci Ci pierwszy węzeł a nie pierwszy element.. co w wielu przypadkach może minąć się z celem ;-)
    Do dodawanych klas dodałbym przedrostek ‘ie-’

  7. Mam pytanie.
    Mam div.code a w nim code/pre, który w IE wyjeżdża poza div.code.
    Jak ma wyglądać definicja szerokości code/pre używająca expressions?

    Tak jak poniżej?

    width: expression(this.parentNode.currentStyle.width + 0);
    
  8. Pytanie troche nie na temat. Jakiego filtru użyłeś do tego obrazka? http://riddle.pl/blog/images/expressions-fixed.png

  9. liviopl: Chyba nie, currentStyle.width zwróci Ci wartość, gdy w CSS albo w HTML masz ustawione coś dla width. Szerokość elementu pobiera się tak:

    width: expression(this.parentNode.offsetWidth + 'px');
    

    Sint: Transform, 70% wielkości bodaj, Obrót o -45°, Sharpen.

  10. Da ktoś rady mi pomóc? Chciałem wywołać obramowanie w IE dla code<select>@code@, ale średnio mi poszło:

    select {
    -ie-xp: expression (
    this.parsed ? 0 : (
    this.style.border = '1px solid red',
    this.parsed = 1)
    );
    border: 1px solid red;
    

    Ktoś wie jak mi pomóc :>?

  11. Riddle właśnie mi żeś życie uratował :)

  12. (Komentarz zmodyfikowany 27.09.2007 o 11:58)

    Mam pytanie czy dzięki expression można sprawić żeby jakiś element miała zmienną szerokość, tnz. np ramka pływająca, która ma height równy 100% – 100px od góry – 100px od dołu ??? To coś jakby zamiast trzech zwykłych ramek, gdzie górna i dolna ramka maja wysokość 100px;, a środkowa * zrobić ramke pływającą z zmienną wysokością.

  13. Człowieku jesteś zaj***y, chylę czoła, chociaż nie lubię

  14. farmazone 14 14 grudnia 2007, 15:13

    elo.
    odnosnie patentu na min-width
    a co z sytuacja gdy do diva chcemy wstawic np. flasha?

    ja uzywam swfobject (www.swffix.org) do osadzania swfow. chodzi mi konkretnie o sytuacje gdy publikuje flasha 100% na 100% i wypelnia on cale okno przegladarki, ale nie chce pozwolic uzytkownikowi na zmniejszenie okna ponizej pewnych wartosci. min-width i min-height swietnie spelniaja zadanie.

    nie moge natomiast poradzic sobie z przeniesieniem na IE. przy zastosowaniu twojego kodu flash ladnie sie pojawia ale pasek przewijania przegladarki juz nie :(

    pozdro

  15. (Komentarz zmodyfikowany 23.12.2007 o 22:21)

    Czy ktoś ma jakąś sugestię dlaczego poniższy kod nie działa? Chciałby dodawać „first-child” z wyjątkiem dzieci blockquote.

    -ie-xp: expression(
      this.parsed ? 0 : (
    
        this.parentNode.nodeName == ‘blockquote’ ? 0 : (
    
        this.className += (this === this.parentNode.firstChild) ? „first-child” : „”,
        this.parsed = 1)
    
      )

    Niestety, efekt jest taki jak w wersji pierwotnej bez warunku z ‘blockquote’.

  16. -ie-xp: expression(
      this.parsed ? 0 : (
        parent = this.parentNode,
        fixMe = (parent.nodeName == 'BLOCKQUOTE') ? 0 : (
          this.className += (this === parent.firstChild) ? 'first-child' : '',
          this.parsed = 1
        )
      )
    )

    Pozdrowienia. :)

  17. Świetny tekst, na pewno się przyda w pracach. Dużo rzeczy omijałem właśnie przez brak wsparcia w starszych przeglądarkach. I fajnie, że wszystko siedzi w CSS. Duże dzięki!

Dodaj komentarz

Do formatowania komentarzy używaj Textile (HTML nie działa). Szczególnie jeśli wklejasz większe fragmenty kodu. W razie niepewności użyj podglądu komentarza.

Wypowiedzi obraźliwe, infantylne oraz nie na temat będą moderowane – pisząc postaraj się zwiększyć wartość dyskusji.

Komentarze nie służą do wysyłania wiadomości albo informowania o błędach, itd. Chcesz coś mi napisać – skontaktuj się.