SitePoint Sponsor

User Tag List

Results 1 to 25 of 26

Threaded View

  1. #1
    The CSS Clinic is open silver trophybronze trophy
    Paul O'B's Avatar
    Join Date
    Jan 2003
    Location
    Hampshire UK
    Posts
    39,797
    Mentioned
    158 Post(s)
    Tagged
    3 Thread(s)

    CSS only Direction aware hover

    Hi,

    Here's something I thought I'd share with you seeing as things have been pretty quiet

    I recently saw a demo using JS to create direction aware CSS hovers and I thought to myself I wonder if that could be done with CSS alone and no script? (I often have these thoughts). I also thought it might be interesting (and of use) to show my thought processes and to see how I ended up with the working version.

    So without further ado here's my CSS only version (modern browsers only).
    Edit:


    New Improved version with suggested edits from Francky in post 11.


    If you mouse in from any direction the hover effect appears displays a message animating from that direction. The technique uses the same message for each image so it's not 4 messages hiding at each edge).

    The Method.

    Obviously css doesn't know anything about the directional movement of the mouse (unlike JS) so the first step was to think up a mechanism that would allow the animation to be triggered when entering the image from any side. I already had in mind a way to do this and the idea was to place 4 small elements around the edges of the image so that they would become active when they were moused over.

    Here's a rough drawing of the first step:
    Img 1:
    dh1.png

    I used 4 "b" elements in the html and absolutely placed them into position around the html. I was later hoping to reduce these elements and perhaps use :before and :after instead but when constructing a demo its usually best to use the simplest approach first and then refine it later. After all it may not work anyway so just keep it simple until it works... or doesn't work. (If you don't like "b" elements then use spans instead but as there were 4 of them it looked messy as spans (the "b" is mostly semantically neutral anyway although - I'm not going to argue this point anyway so use what you feel best and appropriate).

    With these 4 elements in place it is then a simple matter to activate any of the elements individually when hovered.

    e.g.
    Code:
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Untitled Document</title>
    <style type="text/css">
    div{
    	width:150px;
    	height:150px;
    	background;red;
    	border:1px solid #000;
    	position:relative;	
    }
    b {
    	display:block;
    	width:150px;
    	height:20px;
    	position:absolute;
    	left:0;
    	z-index:2;
    }
    b:nth-of-type(1) { top:0 }
    b:nth-of-type(2) { bottom:0 }
    b:nth-of-type(3) {
    	top:0;
    	width:20px;
    	height:150px;
    }
    b:nth-of-type(4) {
    	top:0;
    	width:20px;
    	height:150px;
    	left:auto;
    	right:0
    }
    b:hover{background:red}
    </style>
    </head>
    
    <body>
    <div><b></b><b></b><b></b><b></b></div>
    </body>
    </html>
    Img 2:
    dh2.png

    If you hover at the edge you will see that a small strip at the edge turns red. This is just basic CSS and nothing clever here at all but it does give an indicator of where the mouse is.

    The problem is trying to tie this hover effect in with showing a message and animating the message from that side.

    There are now a few things we need to think about.

    1) The strip we added to the image is only 20px deep so once we go past that strip the hover effect is lost.
    2) If we continue across the image with the cursor then the opposite strip is then triggered and would spoil the effect we are aiming for.
    3) How does this help with sliding a message in from that side?

    Points one and 2 above can be solved in a single step by ensuring that as soon as we hover the little strip we then increase its size and z-index so that it covers the whole image. This will keep the cursor active all the way across the image and at the same time stop the other strips from being triggered.

    All we need to do that is this:
    Code:
    b:hover {
    	height:150px;
    	width:150px;
    	z-index:99;
    	background:red;
    }
    Note that I am using a red background in the explanation but that is only to make it visible for the explanation. This area will be transparent in the full demo.

    Ok now for the hard part:

    We have established an effect when we hover over the image but how to make that effect animate our message across the image?

    So far the html looks like this:
    Code:
    <div><b></b><b></b><b></b><b></b></div>
    Lets add some html for our sliding message.

    Code:
    <div><b></b><b></b><b></b><b></b><strong>This is the sliding message</strong></div>
    So when we hover a "b" element we want to slide in the message (which will be hidden by default). We could use the adjacent sibling selector so that when the "b" element is hovered we can select an adjacent element.

    e.g.
    Code:
    b:hover + strong{styles here..}
    However, that soon gets messy as you would need to start doing things like this:

    Code:
    b:hover + b + b + b + strong{styles here..} 
    b:hover + b + b + strong{styles here..} 
    b:hover + b + strong{styles here..} 
    b:hover + strong{styles here..}
    We can simplify that by using the general sibling selector. This allows us to select the strong element while any of the b elements are hovered.
    Code:
    b:hover ~ strong{styles here..}
    Unfortunately the above method doesn't give us a unique reference to the element being hovered as we need to apply 4 different sets of rules to the strong element depending on which b element was hovered. Therefore we need to identify each b element uniquely which can be done with a class or as I have done in the demo by using :nth-of-type(n).

    Finally we have a way of applying 4 different styles to the strong element (the element that holds the message) depending on which side the mouse approaches.

    Working Code so far:
    Code:
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Untitled Document</title>
    <style type="text/css">
    div{
    	width:150px;
    	height:150px;
    	background;red;
    	border:1px solid #000;
    	position:relative;	
    }
    b {
    	display:block;
    	width:150px;
    	height:20px;
    	position:absolute;
    	left:0;
    	z-index:2;
    }
    b:nth-of-type(1) { top:0 }
    b:nth-of-type(2) { bottom:0 }
    b:nth-of-type(3) {
    	top:0;
    	width:20px;
    	height:150px;
    }
    b:nth-of-type(4) {
    	top:0;
    	width:20px;
    	height:150px;
    	left:auto;
    	right:0
    }
    b:hover {
    	height:150px;
    	width:150px;
    	z-index:99;
    }
    strong{
    	width:150px;
    	height:150px;
    	display:none;
    	text-align:center;
    	color:#fff;	
    }
    b:hover ~ strong {display:block;z-index:99}
    b:nth-of-type(1):hover ~ strong {background:blue}
    b:nth-of-type(2):hover ~ strong {background:green}
    b:nth-of-type(3):hover ~ strong {background:yellow}
    b:nth-of-type(4):hover ~ strong {background:red}
    </style>
    </head>
    <body>
    <div><b></b><b></b><b></b><b></b><strong>This is the sliding message</strong></div>
    </body>
    </html>
    As you can see if you hover the element from any side the message turns a different background colour. This is very close to what we want except that we now need to animate the message from that side as well!

    When I first approached this I thought it could simply be done with transitions. I'll just move the element off to the side and then on hover change its position so that it slides into place. I got pretty close but ultimately failed at the final step. I could animate the message ok but it only animated from where it started and although I could set up a different end position with the hover effect I couldn't specify 4 different starting positions for the message!

    Keyframes to the rescue.

    With keyframes you have the ability to set a "from" starting point for your animation which doesn't necessarily have to conform to where the original element is situated. This allows the message to start in the appropriate place for each animation and then finish up where we want.

    We will need 4 keyframes for each direction which I will call simply up, down, left and right.

    That will leave is with full code that looks like this (Firefox only):
    Code:
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Untitled Document</title>
    <style type="text/css">
    div {
    	width:150px;
    	height:150px;
    	border:1px solid #000;
    	position:relative;
    	margin:20px;
    	overflow:hidden;
    }
    b {
    	display:block;
    	width:150px;
    	height:20px;
    	position:absolute;
    	left:0;
    	z-index:2;
    }
    b:nth-of-type(1) { top:0 }
    b:nth-of-type(2) { bottom:0 }
    b:nth-of-type(3) {
    	top:0;
    	width:20px;
    	height:150px;
    }
    b:nth-of-type(4) {
    	top:0;
    	width:20px;
    	height:150px;
    	left:auto;
    	right:0
    }
    b:hover {
    	height:150px;
    	width:150px;
    	z-index:99;
    }
    strong {
    	width:150px;
    	height:150px;
    	text-align:center;
    	color:#fff;
    	position:absolute;
    	top:-150px;
    	background:red;
    }
    b:nth-of-type(1):hover ~ strong {
     -moz-animation:down .3s ease-in-out forwards;
     -webkit-animation: down .3s ease-in-out forwards;
     animation: down .3s ease-in-out forwards;
    }
    @keyframes down { from {
     transform: translateY(0px);
     opacity:0;
    }
    to {
    	transform: translateY(150px);
    	opacity:1
    }
    }
    b:nth-of-type(2):hover ~ strong {
     -moz-animation:up .3s ease-in-out forwards;
     -webkit-animation: up .3s ease-in-out forwards;
     animation: up .3s ease-in-out forwards;
    }
     @keyframes up { from {
     transform: translateY(300px);
     opacity:0;
    }
    to {
    	transform: translateY(150px);
    	opacity:1
    }
    }
    b:nth-of-type(3):hover ~ strong {
     -moz-animation:left .3s ease-in-out forwards;
     -webkit-animation: left .3s ease-in-out forwards;
     animation: left .3s ease-in-out forwards;
     top:0;
    }
    @keyframes left { from {
     transform: translateX(-150px);
     opacity:0;
    }
    to {
    	transform: translateX(0px);
    	opacity:1
    }
    }
     b:nth-of-type(4):hover ~ strong {
     -moz-animation:right .3s ease-in-out forwards;
     -webkit-animation: right .3s ease-in-out forwards;
     animation: right .3s ease-in-out forwards;
     top:0;
     left:auto;
     right:0;
    }
    @keyframes right { from {
     transform: translateX(150px);
     opacity:0;
    }
    to {
    	transform: translateX(0px);
    	opacity:1
    }
    }
    </style>
    </head>
    
    <body>
    <div><b></b><b></b><b></b><b></b><strong>This is the sliding message</strong></div>
    </body>
    </html>
    As you can see we now have a working demo and the message will fly in from the side that the mouse approaches.

    You will of course need to add the vendor prefixes which I have omitted for brevity but refer to the full example which has the pre-fixes in place. The full example is just tidied up and has images, content and better alignment but uses the same techniques discussed above.

    Pros and Cons

    There are some drawbacks that I can see with this effect over a JS version:

    1) You need the 4 extra elements in the html.

    e.g.
    Code:
    <b></b><b></b><b></b><b></b>
    Other than those elements the html is normal html and needs n other special considerations. Of course you could just add those with Javascript but I guess that defeats the purpose of a CSS only effect.

    I was hoping to use:before and :after to minimise the use of those "b" elements but I couldn't find a reliable way of doing this. Maybe you can come up with a better idea?

    2) Unlike the JS version the animation doesn't return the way it cam from when the mouse is moved out of the element. I can make it always hover upwards on muse out but that ruins the effect. The problem is that it is possible to have an animation effect on mouse out but then we lose the position of the message and it goes back to where it was originally placed (which is why I could only make it go upwards). There may well be a way to fix this issue but I kind of prefer it as it is.

    3 If you remember we created the "trigger" areas with 4 little strips of 20px depth. That means that if we move the mouse quickly to the centre it will jump over the trigger and not be animated. Again, I like this effect because it acts almost as a "hover-intent" and you don't get the messages jumping every time you move your mouse to a menu.

    4 The 20px section in each corner of the image is overlapped by each subsequent trigger area so we don't get a perfect trigger area along each side but again that's just a small issue.

    Pros

    1) CSS only and no script.

    2) It's fun to do.

    3) Looks pretty good.

    Refer to the original demo for the full code as I have not explained all the code in details but leave a message if there's anything you don't understand or indeed if you have some improvements as this has mainly been a proof of concept.

    If you read this far then well done
    Last edited by Paul O'B; Oct 7, 2013 at 10:06.


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •