开发者

Keep image hover button when click using jQuery

开发者 https://www.devze.com 2023-04-10 19:17 出处:网络
I have the following code to provide a hover effect when I hover over a button image: $(\"img.hoverImage\")

I have the following code to provide a hover effect when I hover over a button image:

 $("img.hoverImage")
    .mouseover(function () {
        var src = $(this).attr("src").match(开发者_StackOverflow社区/[^\.]+/) + "_hover.png";
        $(this).attr("src", src);
    })
    .mouseout(function () {
        var src = $(this).attr("src").replace("_hover", "");
        $(this).attr("src", src);
    });

This works great. I now have an additional requirement:

I have three buttons in a row that all have class="hoverImage".

<a class="imageLink" href=""><img class="hoverImage" src="/Content/Images/1.png"/></a>
<a class="imageLink" href=""><img class="hoverImage" src="/Content/Images/2.png"/></a>
<a class="imageLink" href=""><img class="hoverImage" src="/Content/Images/3.png"/></a>

I still want to keep this hover effect but now I also want to change it so when I click on an image, it keeps the hover image shown (even when I mouse out of that image). When I click on another button, it should remove the hover from the other buttons.

NOTE: that clicking on the image links do NOT redirect to a new page (they just call some ajax from a js file)

What is the best way to extend the code below to support this with jQuery? It seems like I need to disable the mouseout handler after the item is clicked but I am trying to avoid having a complicated solution.


You can track active state of clicked image with one of class/data/attr, something like this will do the work.

$("img.hoverImage").mouseover(function(){
  if ($(this).is('.activeImage')) return;
  var src = $(this).attr("src").match(/[^\.]+/) + "_hover.png";
  $(this).attr("src", src);
}).mouseout(function(){
  if ($(this).is('.activeImage')) return; // Skip mouseout for active image
  var src = $(this).attr("src").replace("_hover", "");
  $(this).attr("src", src);
}).click(function(){
  // remove previous active state
  $('.activeImage').not(this).removeClass('activeImage').trigger('mouseout');
  // set new active state
  $(this).addClass('activeImage');
  return false;
});


$("img.hoverImage").click(function() {
    $(this).removeClass("hoverImage").addClass("keepImage");
    $(this).siblings("img.keepImage").removeClass("keepImage").addClass("hoverImage");
});

Basically, you remove the hoverimage class and the functionality of it on click, making the mouseout event not occur when you mouse out. It also restores the hoverImage class to all siblings.

You still might need to restore the non-hover image on siblings in my click handler there though.

Edit: I made you a JSFiddle, and i took a different approach:

http://jsfiddle.net/jqWJN/2/

Basically, you make a few links with class hoverImage, like so:

<a class="hoverImage"> </a>

Then you declare them to be inline-blocks, with a width and height matching that of your image, and give them a background-image of your choosing:

a.hoverImage {
    display: inline-block;
    width: 100px; height: 100px;
    background-image: url(http://placekitten.com/g/100/100);
}

Then, you make a CSS property that gives them a new background-image when they are hovered, and when they have the class "keepImage":

a.hoverImage:hover, a.hoverImage.keepImage {
    background-image: url(http://placekitten.com/100/100);
}

Naturally, you'll have to make a unique one for each link if you want different pictures.

Finally, you make a small snippet of JQuery that adds the class keepImage to them when they're clicked, and removes the class from any siblings who has it:

$("a.hoverImage").click(function() {
    $(this).siblings(".keepImage").removeClass("keepImage");
    $(this).addClass("keepImage");
});

If you'd like this to work on a broader scale than just one set of siblings, like say, page-wide, just replace this:

$(this).siblings(".keepImage")

with this:

$(".keepImage")

Good luck!


I think it will be easier if you use "div"s with image as background. Specify the div width and height to be the same as the background-image and apply it. This is especially applicable in your case where you are using these images just as a placeholder for someone to click on it.

Once this is changed specify :hover CSS fix. The new requirement that you have is related to identifying currently active image. For this,

     $("<<placeholder - selector>>").click(function(){
           $(this).addClass("active").siblings().removeClass("active");

     });

If active class is already present in siblings, it will be removed and the current one will be highlighted. Hope this solves the issue


I personally would make quite a few changes:

First, I wouldn't depend on the logic of swapping file name chunks. You may want to change your conventions later on and don't want to have to scour through your JavaScript correcting things. I would do something along the following lines:

<img src="/Content/Images/1.png"
     data-active-image="/Content/Images/1_hover.png" />

If in the future you want to expand this, the sky is the limit:

<img src="/Content/Images/1.png"
     data-active-image="/Content/Images/1_hover.png"
     data-hover-image="/Content/Images/1_hover.png"
     data-disabled-image="/Content/Images/1_hover.png" />

Second, I would break things out. I would have event code, and then image selection code. Here's what I put together for the image selection code:

$.fn.updateImage = function(hovering) {
    return this.each(function() {
        var $el = $(this);
        var active = (hovering || $el.hasClass('active'));

        var image = active ? 'activeImage' : 'inactiveImage';

        if(!$el.data('inactiveImage'))
            $el.data('inactiveImage', $el.attr('src'));

        $el.attr('src', $el.data(image));
    });
};

It accepts a boolean value from the event code to say whether the mouse is over or not. It does a check to see if the element has the active class or not. If it has the active class, or is being hovered, it is true. We use this to choose which source to use. The next bit is used to cache the original source in the data-inactive-image attribute. Finally, we set the source.

Next we have the event code. Since we're dealing with a sort of radio-button style grouping, I would break this out so you could have multiple image groups per page. On click, we make all the images from the original selector inactive and make the current image active using the active class mentioned earlier:

$.fn.makeHoverGroup = function() {
    var $group = this;

    return this
        .mouseover(function() {
            $(this).updateImage(true);
        })
        .mouseout(function() {
            $(this).updateImage(false);
        })
        .click(function() {
            $group.not(this).removeClass('active').updateImage(false);
            $(this).addClass('active').updateImage(true);
        });
};

To wire it all together, we have just one line left:

$('.hoverImage').makeHoverGroup();

As an added bonus, CSS knows everything that JavaScript does. Hovered images have the .hoverImage:hover selector (a:hover .hoverImage for IE6 support) and active images have the .hoverImage.active selector.

You can see an example here: http://fiddle.jshell.net/nDdzA/1/

Update Fixed remove/re-add on click.


Here is example code on how to achieve what you want: http://jsfiddle.net/wCL2g/

The javascript code:

$("img.hoverImage").mouseover(function() {

    var src = $(this).attr("src").match(/[^\.]+/) + "_hover.png";
    $(this).attr("src", src);

    // add class to state the element is hovered
    $(this).addClass('hover');
}).mouseout(function() {
    // don't trigger this function if element has clicked state
    if ($(this).hasClass('click')) {
        return;
    }

    var src = $(this).attr("src").replace("_hover", "");
    $(this).attr("src", src);

    // remove hover class
    $(this).removeClass('hover');
}).click(function(e) {
    // disable the default click event
    e.preventDefault();

    $(this).parent()              // from the parent element
           .siblings()            // find all the siblings elements <a>'s
           .find('img')           // find <img> inside each sibling
           .removeClass('hover')  // remove hover class
           .removeClass('click'); // remove click class

    // trigger the mouse over event for the image tag
    $(this).addClass('click').trigger('mouseover');
});

Hope this helps


I've taken the workings of the event handlers into functions for the sake of readability. This could probably be cleaned up a bit to go in production code but it's a minor point for something this size.

When an element is clicked it gains the class "isClicked", the rest have it removed. All the matching elements are passed to the unHover function. The unHover function is that same as your mouseout handler except it only works on elements lacking the isClicked class.

$("img.hoverImage")
    .mouseover(function () {
        makeHoverImage(this);
    })
    .mouseout(function () {
        unHoverImage(this);
    })
    .click(function(){
        makeActive(elem);
    });

function makeActive(elem){
    $("img.hoverImage").removeClass("isClicked");
    $(elem).addClass("isClicked");
    $("img.hoverImage").each(function(index,elem){
        unHoverImage(elem);
    });
}

function makeHoverImage(elem){
    var src = $(this).attr("src").match(/[^\.]+/) + "_hover.png";
    $(this).attr("src", src);
}

function unHoverImage(elem){
    if (!$(elem).hasClass("isClicked")){
        var src = $(this).attr("src").replace("_hover", "");
        $(this).attr("src", src);
    }
}


I would set up a global string that stores the current image src. Then use that in the mouseout function to test if the current image has been clicked, if so you bypass the usual mouseout procedure.

On mouse click, the function finds the image with the src of currentImage, resets the currentImage variable after it has been found (allowing the mouseout function to run correctly) and manually performs the mouseout. Afterwards it sets the currentImage variable to the image src (as mentioned above).

This solution is also handy as it fits into your queue and has minimal impact on JS and no impact on CSS and HTML.

Check out the code below and let me know if you have any questions :)

var currentImage = "0";

$("img.hoverImage")
.click(function(){
    $("img.hoverImage").each(function() {
        if($(this).attr("src")==currentImage){
            currentImage = "0";
            $(this).mouseout();
        }
    });
    currentImage = $(this).attr("src");
})
.mouseover(function () {
    var src = $(this).attr("src").match(/[^\.]+/) + "_hover.png";
    $(this).attr("src", src);
})
.mouseout(function () {
    if($(this).attr("src") != currentImage){
        var src = $(this).attr("src").replace("_hover", "");
        $(this).attr("src", src);
    }
});

Thanks! :)


Use jquery mousedown & mouseup.

$("img.hoverImage")
.mouseenter(function () {
    var src = $(this).attr("src").match(/[^\.]+/) + "_hover.png";
    $(this).attr("src", src);
})
.mouseleave(function () {
    var src = $(this).attr("src").replace("_hover", "");
    $(this).attr("src", src);
});
.mousedown(function () {
   $(this).trigger('mouseleave');
})
.mouseup(function () {
     $(this).trigger('mouseenter');
});


Many people have suggested to use jQuery attribute and metadata handling for this. Here I just propose to use a global variable. However, there are few issues you probably faced, so let me explain the solutions:

(1). If you put a global variable, beware of the href="" in your anchors. If you trigger a reload of the page you are back in square zero, not good. So I removed that.

(2). You want to check the global variable in the mouseover and mouseout to do something like

if <triggered from current> return;   // disabling check    

problem is that in the click function you want to do something like

if <there is a current> current.trigger('mouseout');
<set new current>
current.trigger('mouseover')

but as we are disabling mouseout and mouseover by the disabling check then triggering those doesn't work.

You have two options here: a) you can set current to null, trigger those two events and then set current to the one clicked; b) Or you can isolate the over/out effect from the trigger so you can call it in two places.

Option (a) is a more complex click handler but simpler mouse handler. Option (b) requires little more refactoring, but if you prefer going down the path of using other jQuery features, it is probably a good idea.

(3). Because I am not using jQuery idioms but your approach, one needs to be careful with the regular expression so it matches what it should be. I did this to.

(4). The code below attaches the click to the anchors as in you mentioned (although some solutions mention the images themselves.) The issue here is how you link the clicked object to the corresponding image. In the code below jQuery's find() is the clue.

(5). It is best practice in jQuery not to use global variables, i.e. variables at the window object level. You can still obtain the same result declaring the variable above the closures involved as illustrated below. Also, people like to use $ for variables holding jQuery wrapped objects and I did so.

(6). Finally, beware of object comparison. The code below makes sure we compare DOM objects, so use jQuery's get().

So, this is the amended HTML (removed href):

<a id="anchor1" class="imageLink" ><img class="hoverImage" src="images/1.png"/></a>
<a id="anchor2" class="imageLink" ><img class="hoverImage" src="images/2.png"/></a>
<a id="anchor3" class="imageLink" ><img class="hoverImage" src="images/3.png"/></a>

(sorry, image URLs are not as yours, I actually tested the code below)

Approach (a):

<script type="text/javascript">
$(function() {
    var clickedImg = null; // this is common to all closures below
    $("img.hoverImage")
        .mouseover(function () {
            var $img = $(this);
            if ($img.get(0) == clickedImg) return;
            // note the improved matching !
            var src = $img.attr("src").match(/[^_\.]+/) + "_hover.png"; 
            $img.attr("src", src);
        })
        .mouseout(function () {
            var $img = $(this);
            if ($img.get(0) == clickedImg) return;
            var src = $img.attr("src").replace("_hover","");
            $img.attr("src", src);
        });
         $("a.imageLink").click(function() {
            var oldClicked = clickedImg;
            clickedImg = null;                         // set to null to trigger events
            if (oldClicked) $(oldClicked).mouseout();
            var newClicked = $(this).find('img').get(0);
            $(newClicked).mouseover();
            clickedImg = newClicked;                   // redefine at the end

            alert($(this).attr('id') + " clicked"); // ajax call here
            });
});
</script>

and approach (b):

<script type="text/javascript">
$(function() {
   var clickedImg = null; // same global
   // refactor the mouse over/out - you could use other jQuery ways here
   function doOver($img) {
       var src = $img.attr("src").match(/[^_\.]+/) + "_hover.png";
       $img.attr("src", src);
   }
   function doOut($img) {
       var src = $img.attr("src").replace("_hover","");
       $img.attr("src", src);
   }
   $("img.hoverImage")
        .mouseover(function () {
                var $img = $(this);
                if ($img.get(0) == clickedImg) return;
                doOver($img);
        })
        .mouseout(function () {
                var $img = $(this);
                if ($img.get(0) == clickedImg) return;
                doOut($img);
        });
   $("a.imageLink").click(function() {
        if (clickedImg) doOut($(clickedImg));
        clickedImg = $(this).find('img').get(0);
        doOver($(clickedImg));
        alert($(this).attr('id') + " clicked"); // presumably your ajax call here
   });
});
</script>

Option (a) preserves your structure, but I think option (b) should be your next step and it is my preference.


If your links don't change window location you can go on with Andreas' answer or something similar. If they do, it looks more like a job for the server side script. I don't know which one are you using so I'll post an example in php.

<?php foreach ($links as $i => $link): ?>
<a class="imageLink" href="<?php echo $link ?>">
  <img class="hoverImage" src="/Content/Images/<?php echo ($i == $selected) ? $i . "_hover" : $i ?>.png"/>
</a>
<?php endforeach ?>
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号