How can I make CSS hover effect touch friendly?

I have some CSS that swaps out an image when a user mouses over it, then fades the original image back in when the user mouses away again.

<div class="swap>
  <a>
    <img src="https://farm9.staticflickr.com/8242/8558295633_f34a55c1c6_b.jpg">
  </a>
</div>

and:

.swap {
  background-image: url('https://farm9.staticflickr.com/8382/8558295631_0f56c1284f_b.jpg');
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
}

.swap a {
  display: block;
}

.swap a img {
  opacity: 1;
  height: auto;
  display: block;
  transition: all .9s ease-in-out;
}

.swap a:hover img {
  opacity: 0;
  transition: all .6s ease-in-out;
}

This is taken from @SamA74’s answer in this thread.

This works fine on desktop, but I’d like to make the image toggle on click too, so that touch devices are not left out.

I’ve tried targeting the anchor’s active state:

.swap a:hover img,
.swap a:active img {
  ...
}

And adding an onclick attribute to the containing div:

<div class="swap" onclick="void(0)">

But neither of these solutions worked on the iPad2 I am testing on. What am I missing?

Here is a CodePen demonstrating my problem:

Hi there Pullo,

Unfortunately, I do not possess the means of testing, but
Javascript touchstart and touchend event handlers are
fired when a touch point is placed and removed from the
touch surface.

Perhaps they would suit your requirements.

coothead

Thanks Coothead. I should have mentioned I’m looking for a pure CSS solution (if possible). Sorry :slight_smile:

Not sure if this helps or not, I don’t have any iThingies to test on.
But I generally pair :hover with :focus, it’s mainly for the benefit of keyboard users, but I’m not sure if a touch puts focus on an <a>.

No dice :frowning: The picture toggles once, but then stays toggled.

You think it’d help to put it on the div? I’ll give that a try.

Divs by default can’t have focus, I think you need to give it a tabindex so it can.
Focus should stay put until you move it elsewhere, so in the case of a tap, you would have to tap on another focusable element to shift it.

Tried giving the div a tabindex and adding:

.swap a:hover img,
.swap:focus a img {
  opacity: 0;
  transition: all .6s ease-in-out;
}

Works as expected with a keyboard. Still no dice with teh iPad.

Hi there Pullo,

perhaps this…

.swap a:hover img,
.swap a:active img {
  opacity: 0;
  transition: all .6s ease-in-out;
}

coothead

Had that in my first post :slight_smile:

Hi there Pullo,

It seems that you need Javascript then. :wonky:

coothead

Yeah man, rapidly approaching that conclusion myself. Just wanted to be sure, first.

Hover is treated as a first touch by iOS devices (and others) but there is no ‘ un hover’ unless you touch/ click somewhere else so that the original loses its hover/ focus effect.

Once you touch the element it’s touched until you you touch somewhere else. That means you can’t do a hover effect as such for mobile (although mileage may vary depending on device).

As suggested above you could do similar in Js with touchstart and touchend instead.

I did have a codepen of this effect but as I am on holiday I don’t have my login details to hand :slight_smile:

4 Likes

Thanks for the explanation, Paul. I’ll go the JS route, but if at some point you manage to dig out the CodePen, it’d be great if you could post the link here.

Have a nice holiday!

Was it this one?

1 Like

Hi there Pullo,

here is a Vanilla Javascript example…

<!DOCTYPE HTML>
<html lang="en">
<head>

<meta charset="utf-8">
<meta name="viewport" content="width=device-width,height=device-height,initial-scale=1">

<title>untitled document</title>

<link rel="stylesheet" href="screen.css" media="screen">

<style media="screen">
body {
    background-color: #f0f0f0;
    font: 1em/160% verdana, arial, helvetica, sans-serif;
 }

.swap {
  background-image: url('https://farm9.staticflickr.com/8382/8558295631_0f56c1284f_b.jpg');
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
  width: 12.5em;
 }

.swap a {
  display: block;
 }

.swap  a img {
  opacity: 1;
  width: 12.5em;
  height: auto;
  display: block;
  transition: all 0.6s ease-in-out;
 }

 .swap a:hover img {
  opacity: 0;
  transition: all 0.6s ease-in-out;
}
</style>

</head>
<body>
 
 <div class="swap">
   <a id="pic">
     <img src="https://farm9.staticflickr.com/8242/8558295633_f34a55c1c6_b.jpg" alt="tiger image">
   </a>
 </div>

<script>
(function( d ) {
   'use strict';

   var el,eln;

   d.getElementById( 'pic' ).addEventListener( 'touchstart', 
function(){ 
   el = this.firstChild;
   eln = el.nextSibling;

   if ( el.nodeType === 1 ){
        el.style.opacity = 0;
        el.style.transition = 'all .6s ease-in-out';
      }
   else {
        eln.style.opacity = 0;
        eln.style.transition = 'all 0.6s ease-in-out';
      }
    },false );

   d.getElementById( 'pic' ).addEventListener( 'touchend', 
function(){
   if ( el.nodeType === 1 ){
        el.style.opacity = 1;
        el.style.transition = 'all .6s ease-in-out';
     }
   else {
        eln.style.opacity = 1;
        eln.style.transition = 'all 0.6s ease-in-out';
      }
    },false  );
}( document ));
</script>

</body>
</html>

coothead

2 Likes

Hey Coothead,

Thanks for the reply. I appreciate you taking the time to get a working example up and running.

Fortunately, JS is much more my thing than CSS :slight_smile:

I’m not the biggest fan of manipulating CSS through JS, nor things like el.nodeType === 1, as when I come to revisit this in a couple of months, I will have forgotten what that was meant to do.

Consequently, a better approach might be to conditionally apply a hover class using JS, as opposed to targeting the :hover psuedo selector.

So if we change this:

.swap a:hover img {
  opacity: 0;
  transition: all .6s ease-in-out;
}

to:

.swap a.hover img {
  opacity: 0;
  transition: all .6s ease-in-out;
}

Then reaching for jQuery, we’d need to do something like:

$(".swap a").on("mouseenter mouseleave touchstart", function(e){
  if(e.type == 'touchstart') {
    $(this).off('mouseenter mouseleave');
  }

  $(this).toggleClass("hover");
});

Note, that if a touchscreen is detected, it is necessary to remove the mouseenter and mouseleave events to stop the fade effect firing twice.

However, for once I’m not using jQuery on the page I’m working on, so vanilla JS (and ES5 as I am testing on an old ipad) would be a better solution.

This gives us:

function toggleImage(e) {
  if (e.type == "touchstart") {
    link.removeEventListener("mouseenter", toggleImage);
    link.removeEventListener("mouseleave", toggleImage);
  }

  link.classList.toggle("hover");
}

var link = document.querySelector(".swap a");

["mouseenter", "mouseleave", "touchstart"].forEach(function (event) {
  link.addEventListener(event, toggleImage, false);
});

Which works exactly as intended.

Thanks again for your help.

2 Likes

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.