Blog-Archiv

Samstag, 19. Dezember 2015

Receiving CSS Events

"Event" is a quite diffuse term. It is something that happens unpredictably, we need to "listen" (wait) for it when we want to act on it. For example, operating systems generate events when you use mouse or keyboard.

Disambiguation: in the Java world, events also could be understood as a method-call on 0-n objects that implement a certain listener-interface and have registered for being notified. This is an event abstraction, used to implement loosely coupled parts of an application. The event dispatching mechanism then could be replaced by e.g. network communication at a later time.

Web-pages send events to JavaScript listeners that have registered themselves by calling browserObject.addEventListener(eventType, listenerFunction), for example

window.addEventListener('scroll', function(event) { console.log('scroll event '+event); });

But we can not register CSS event listeners. Can we?

CSS Rule-Sets Targeting State Changes

You may have seen "Pure CSS" solutions. These actually react upon user input events and alter the page accordingly, for example by opening a menu. And there is no JavaScript in that page that does this! So how does this work?

The idea is to define a CSS-rule that is fulfilled sometimes when a certain state is obtained. For example, when the mouse hovers a certain element (= state), we want to make its font bold. We can detect state changes using CSS pseudo-classes. These are the trailing optional :xxx expressions in CSS selectors (not to be confused with pseudo-elements, which are ::xxx).

Following page defines different colours for active, focus, hover, target pseudo-classes. Paste the page into some file and view it by using the file:/// protocol in the browser address line. Move the mouse in and out the page, over elements, click on elements, and watch how pseudo-classes change the page.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <title>Pseudo Classes Test</title>
    
    <style type="text/css">
      :active {
        color: green;
      }
      :focus {
        border: 2px solid red;
      }
      :hover {
        background-color: lightGray;
      }
      :target {
        outline: 2px solid blue;
      }
    </style>
  </head>
  
  <body>
    
    <p>Non-focusable element: p</p>
    
    <div>Non-focusable element: div</div>
    
    <button>HTML Button &#9776;</button>
      
    <input type="button" value="Native Input Button &#9776;" />
    
    <a href="#linkTarget">Hyperlink anchor</a>
    <p id="linkTarget">This is the link's target</p>
    
  </body>

</html>

You can find a list of pseudo classes at many places on the internet. Really useful for animating web pages are only few:

  • :focus ... becomes true when a focusable element is clicked
  • :target ... becomes true when a hyperlink pointing to an internal target is clicked

But even these leak:

  1. The :focus pseudo-class actually gives you a mouse click for input-elements like a button. You can then open a menu. But when the user again clicks to the button, you can not close the menu through CSS. Just a click elsewhere on the page will release the :focus state. Finding out this is unfortunately up to the user.

  2. To be able to use :target, you need to define hyperlinks. The browser would scroll down to the link's internal target element as soon as it was clicked, which may not be what you intended.

  3. When you use :target, the internal link-target will appear in the browser address line, and the browser's "Back" button then would not do what the user expects.

Pure CSS solutions mostly need getting used to, which is not an option in our world of increasing comfort. I do not know your usability preferences, but hover-menus are definitely not what I consider to be ergonomic, they are too intrusive.

CSS State Change Receipt Examples

1. :focus Popup

Catching a click on a button provides the opportunity to make an element visible that was invisible before. Mind that only few HTML elements are initially focusable: input, select, button, textarea, iframe, a ("anchor", the hyperlink element). But you can make any element focusable by adding the tabindex attribute with a value >= 0 to it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    
    <style type="text/css">
      .focusPopup {
        max-height: 0;
        max-width: 0;
        overflow: hidden; /* else would be visible despite zero height/width */
      }
      .focusTrigger:focus + .focusPopup, .focusPopup:hover {
        max-height: 120em;
        max-width: 120em;
        background-color: lightGray;
      }
    </style>
    
  </head>
  
  <body>
      
    <div>
      <input class="focusTrigger" type="button" value="&#9776;" title="Click me!">
      
      <div class="focusPopup">
        <p>Focus One</p>
        <p>Focus Two</p>
        <p>Focus Three</p>
      </div>
    </div>
    
  </body>
 
</html>


Source code explanations:

Any input class="focusTrigger" are focusable elements we install our pseudo-class listener on. Initially we set any focusPopup to invisible by defining their max-width / max-height as zero.

The CSS rule .focusTrigger:focus + .focusPopup describes the direct follower element (sibling) of the focusTrigger in case the button has received the user input focus (mouse click). So when the pseudo-class becomes true, this rule changes the max-width / max-height properties of the focusPopup to make it visible.

Additionally we do not want to close the focusPopup when it receives a click, because its focusTrigger would lose the :focus pseudo-class then. So I also defined a rule that does the same as .focusTrigger:focus + .focusPopup, but becomes true on mouse-hovering the focusPopup. This results in the CSS alternative .focusTrigger:focus + .focusPopup, .focusPopup:hover.

I used max-width / max-height instead of width / height to avoid a fixed size for the focusPopup. I haven't tested this on all browsers, but this seems to make the content take its "preferred size". Originally this menu was created as slide-menu, being fixed on browser viewport, hovering the page content. When using this as push-menu, you could also use display: none; and display: block; instead of max-width / max-height. But mind that the display can not be animated by e.g. transition: 1s;, animation you could achieve only with some size-property.

Mind that this solution won't work when the focusPopup is not the direct follower of focusTrigger!

2. :target Tabs

This example shows how tabs can be implemented without JavaScript. But mind that clicking on an internal link

  1. will put a bookmark into the browser address line and thus add to browser history
  2. will scroll down to the target of the link when the page is scrollable

Further you can not make the first tab initially visible.
So you may find this solution to be not so useful. Anyway, here is the example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    
    <style type="text/css">
      .linkedTab {
        display: none;
      }
      .linkedTab:target { /* mind that the page will move to the start of the clicked link when it is scrollable! */
        display: block;
      }
    </style>
    
  </head>
  
  <body>
      
    <div>
    
      <a href="#linkedTab1" title="Click me!">Tab-1</a>
      <a href="#linkedTab2" title="Click me!">Tab-2</a>
      <a href="#linkedTab3" title="Click me!">Tab-3</a>
      
      <div id="linkedTab1" class="linkedTab">
        <p>One</p>
      </div>
      
      <div id="linkedTab2" class="linkedTab">
        <p>Two</p>
      </div>
      
      <div id="linkedTab3" class="linkedTab">
        <p>Three</p>
      </div>
      
    </div>
    
  </body>

</html>


Source code explanations:

There are three hyperlinks on the page. Each points to a different internal target HTML-id: a href="#linkedTab1". The target itself is the tab, given as element with that HTML-id. All of them are initially invisible by the rule .linkedTab.

When the user clicks on a hyperlink, the CSS pseudo-class :target becomes active on the target element (which is the tab). Then the rule .linkedTab:target applies and sets the tab visible. The rule .linkedTab:target is stronger than .linkedTab because it has a higher specifity.

Conclusion

Pure CSS solutions are not really user-friendly. CSS is more for colors, fonts, borders, and maybe also for layout. Building menus or tabs on CSS always leaks somehow (which does not mean that we shouldn't use pseudo-classes).




Keine Kommentare: