How to Display Hover Image on Product Listing Page in Magento 2

Have you ever got a requirement to display another image on the hover of the main image on the Product listing page in Magento 2? I know your answer is Yes. This is a common requirement of many Magento 2 merchants.

What is the solution? Well, there are many solutions. You can achieve this functionality using third-party extensions, using a custom made extension, or utilizing an in-built Magento 2 product image class.

We will see different solutions for display a hover image on the product listing page in Magento 2.

Add Hover Image Using Modules

There are a couple of modules available in the Magento marketplace to implement hover image. Here we have listed a few modules.

We all know free extensions are not providing great support. If your client is concern about the support you should go with the paid extension.

Add Hover Image in Magento 2 Programmatically in Custom Module

If you don’t want to go with the third-party module, here is the way to implement hover functionality in your own module.

Here I’m adding only important files of the module, assuming you have already developed a basic Magento 2 module.

Step 1: Add hover_image product attribute to save the hover image for particular product. Create AddHoverImageProductAttribute.php file under app/code/Codextblog/Productimagehover/Setup/Patch/Data directory. Add below code in the file.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Codextblog\Productimagehover\Setup\Patch\Data;

use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\Product\Attribute\Frontend\Image;
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;
use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Framework\Setup\Patch\PatchRevertableInterface;

class AddHoverImageProductAttribute implements DataPatchInterface, PatchRevertableInterface
{

    /**
     * @var ModuleDataSetupInterface
     */
    private $moduleDataSetup;
    /**
     * @var EavSetupFactory
     */
    private $eavSetupFactory;

    /**
     * Constructor
     *
     * @param ModuleDataSetupInterface $moduleDataSetup
     * @param EavSetupFactory $eavSetupFactory
     */
    public function __construct(
        ModuleDataSetupInterface $moduleDataSetup,
        EavSetupFactory $eavSetupFactory
    ) {
        $this->moduleDataSetup = $moduleDataSetup;
        $this->eavSetupFactory = $eavSetupFactory;
    }

    /**
     * {@inheritdoc}
     * @throws LocalizedException
     * @throws \Zend_Validate_Exception
     */
    public function apply()
    {
        $this->moduleDataSetup->getConnection()->startSetup();
        /** @var EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
        $eavSetup->addAttribute(
            Product::ENTITY,
            'hover_image',
            [
                'type' => 'varchar',
                'label' => 'Hover',
                'input' => 'media_image',
                'frontend' => Image::class,
                'required' => false,
                'backend' => '',
                'sort_order' => '30',
                'global' => ScopedAttributeInterface::SCOPE_STORE,
                'default' => null,
                'visible' => true,
                'user_defined' => true,
                'searchable' => false,
                'filterable' => false,
                'comparable' => false,
                'visible_on_front' => false,
                'unique' => false,
                'apply_to' => '',
                'used_in_product_listing' => true
            ]
        );

        $entityTypeId = $eavSetup->getEntityTypeId(Product::ENTITY);
        $attributeSetIds = $eavSetup->getAllAttributeSetIds($entityTypeId);
        foreach ($attributeSetIds as $attributeSetId) {
            $groupId = $eavSetup->getAttributeGroupId($entityTypeId, $attributeSetId, "image-management");
            $eavSetup->addAttributeToGroup(
                $entityTypeId,
                $attributeSetId,
                $groupId,
                'hover_image'
            );
        }

        $this->moduleDataSetup->getConnection()->endSetup();
    }

    public function revert()
    {
        $this->moduleDataSetup->getConnection()->startSetup();
        /** @var EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
        $eavSetup->removeAttribute(Product::ENTITY, 'hover_image');

        $this->moduleDataSetup->getConnection()->endSetup();
    }

    /**
     * {@inheritdoc}
     */
    public function getAliases()
    {
        return [];
    }

    /**
     * {@inheritdoc}
     */
    public static function getDependencies()
    {
        return [];
    }
}

Step 2: Override ImageFactory class using preference to add hover image data. Create di.xml under app/code/Codextblog/Productimagehover/etc directory.

<?xml version="1.0" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Catalog\Block\Product\ImageFactory" type="Codextblog\Productimagehover\Rewrite\Magento\Catalog\Block\Product\ImageFactory"/>
</config>

Step 3: Add ImageFactory.php class under app/code/Codextblog/Productimagehover/Rewrite/Magento/Catalog/Block/Product directory. Add below code.

<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Codextblog\Productimagehover\Rewrite\Magento\Catalog\Block\Product;

use Magento\Catalog\Block\Product\Image as ImageBlock;
use Magento\Catalog\Helper\Image as ImageHelper;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\Product\Image\ParamsBuilder;
use Magento\Catalog\Model\View\Asset\ImageFactory as AssetImageFactory;
use Magento\Catalog\Model\View\Asset\PlaceholderFactory;
use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\View\ConfigInterface;

class ImageFactory extends \Magento\Catalog\Block\Product\ImageFactory
{
    /**
     * @var ConfigInterface
     */
    private $presentationConfig;

    /**
     * @var AssetImageFactory
     */
    private $viewAssetImageFactory;

    /**
     * @var ParamsBuilder
     */
    private $imageParamsBuilder;

    /**
     * @var ObjectManagerInterface
     */
    private $objectManager;

    /**
     * @var PlaceholderFactory
     */
    private $viewAssetPlaceholderFactory;

    /**
     * @var mixed|null
     */
    private $hoverImage;

    public function __construct(
        ObjectManagerInterface $objectManager,
        ConfigInterface $presentationConfig,
        AssetImageFactory $viewAssetImageFactory,
        PlaceholderFactory $viewAssetPlaceholderFactory,
        ParamsBuilder $imageParamsBuilder
    ) {
        $this->objectManager = $objectManager;
        $this->presentationConfig = $presentationConfig;
        $this->viewAssetPlaceholderFactory = $viewAssetPlaceholderFactory;
        $this->viewAssetImageFactory = $viewAssetImageFactory;
        $this->imageParamsBuilder = $imageParamsBuilder;
    }

    /**
     * Retrieve image custom attributes for HTML element
     *
     * @param array $attributes
     * @return string
     */
    private function getStringCustomAttributes(array $attributes): string
    {
        $result = [];
        foreach ($attributes as $name => $value) {
            if ($name != 'class') {
                $result[] = $name . '="' . $value . '"';
            }
        }
        return !empty($result) ? implode(' ', $result) : '';
    }

    /**
     * Retrieve image class for HTML element
     *
     * @param array $attributes
     * @return string
     */
    private function getClass(array $attributes): string
    {
        return $attributes['class'] ?? 'product-image-photo';
    }

    /**
     * Calculate image ratio
     *
     * @param int $width
     * @param int $height
     * @return float
     */
    private function getRatio(int $width, int $height): float
    {
        if ($width && $height) {
            return $height / $width;
        }
        return 1.0;
    }

    /**
     * Get image label
     *
     * @param Product $product
     * @param string $imageType
     * @return string
     */
    private function getLabel(Product $product, string $imageType): string
    {
        $label = $product->getData($imageType . '_' . 'label');
        if (empty($label)) {
            $label = $product->getName();
        }
        return (string) $label;
    }

    public function create(Product $product, string $imageId, array $attributes = null): ImageBlock
    {

        $viewImageConfig = $this->presentationConfig->getViewConfig()->getMediaAttributes(
            'Magento_Catalog',
            ImageHelper::MEDIA_TYPE_CONFIG_NODE,
            $imageId
        );

        $imageMiscParams = $this->imageParamsBuilder->build($viewImageConfig);
        $originalFilePath = $product->getData($imageMiscParams['image_type']);

        if (!empty($product->getData('hover_image'))) {
            $this->hoverImage = $product->getData('hover_image');
            $_imageAsset = $this->viewAssetImageFactory->create(
                [
                    'miscParams' => $imageMiscParams,
                    'filePath' => $this->hoverImage,
                ]
            );
        }

        if ($originalFilePath === null || $originalFilePath === 'no_selection') {
            $imageAsset = $this->viewAssetPlaceholderFactory->create(
                [
                    'type' => $imageMiscParams['image_type']
                ]
            );
        } else {
            $imageAsset = $this->viewAssetImageFactory->create(
                [
                    'miscParams' => $imageMiscParams,
                    'filePath' => $originalFilePath,
                ]
            );
        }

        $attributes = $attributes === null ? [] : $attributes;

        $data = [
            'data' => [
                'template' => 'Magento_Catalog::product/image_with_borders.phtml',
                'image_url' => $imageAsset->getUrl(),
                'hover_image_url' => isset($_imageAsset) ? $_imageAsset->getUrl() : '',
                'hover_class' => 'hover_image',
                'width' => $imageMiscParams['image_width'],
                'height' => $imageMiscParams['image_height'],
                'label' => $this->getLabel($product, $imageMiscParams['image_type']),
                'ratio' => $this->getRatio($imageMiscParams['image_width'], $imageMiscParams['image_height']),
                'custom_attributes' => $this->getStringCustomAttributes($attributes),
                'class' => $this->getClass($attributes),
                'product_id' => $product->getId()
            ],
        ];

        return $this->objectManager->create(ImageBlock::class, $data);
    }
}

Step 4: Add code and css in frontend template to hide/show image on hover. Override image_with_borders.phtml under your custom theme directory app/design/frontend/vendor/theme/Magento_Catalog/templates/product directory. Add below code.

<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
?>
<?php /** @var $block \Magento\Catalog\Block\Product\Image */ ?>

<span class="product-image-container"
      style="width:<?= $block->escapeHtmlAttr($block->getWidth()) ?>px;">
    <span class="product-image-wrapper"
          style="padding-bottom: <?= ($block->getRatio() * 100) ?>%;">
        <img class="<?= $block->escapeHtmlAttr($block->getClass()) ?>"
            <?= $block->escapeHtmlAttr($block->getCustomAttributes()) ?>
            src="<?= $block->escapeUrl($block->getImageUrl()) ?>"
            max-width="<?= $block->escapeHtmlAttr($block->getWidth()) ?>"
            max-height="<?= $block->escapeHtmlAttr($block->getHeight()) ?>"
            alt="<?= /* @noEscape */ $block->stripTags($block->getLabel(), null, true) ?>"/>
        <?php if (!empty($block->getHoverImageUrl())) :?>
        <img class="<?= $block->escapeHtmlAttr($block->getClass()) ?>
        <?= $block->escapeHtmlAttr($block->getHoverClass()) ?>"
            <?= $block->escapeHtmlAttr($block->getCustomAttributes()) ?>
            src="<?= $block->escapeUrl($block->getHoverImageUrl()) ?>"
             max-width="<?= $block->escapeHtmlAttr($block->getWidth()) ?>"
             max-height="<?= $block->escapeHtmlAttr($block->getHeight()) ?>"
             alt="<?= /* @noEscape */ $block->stripTags($block->getLabel(), null, true) ?>"/>
        <?php endif;?>
    </span>
</span>
<style type="text/css">
    .hover_image {
        display: none;
        position: absolute;
        top: 0;
        left: 0;
        z-index: 99;
    }
    .product-item-info:hover .hover_image {
        display: inline;
    }
</style>

You should move the above css into an external .less file inside your theme.

That’s it, now you can see the hover image attribute while selecting the product image in the backend.

After setting the image, you will able to see the hover image on each and every Magento 2 default listing.

If you have developed your own module and you are not utilizing the default image block then you can get the hover image data using the below code.

<?php
/**
* @var \Magento\Framework\Pricing\Helper\Data
*/
protected $priceHelper;

$this->imageHelper->init($product, 'category_page_grid')->getUrl();

Main advantage of using product attribute as a hover image is, you can use it while importing product images using CSV.

Conclusion

To display hover image on product listing page is very easy and don’t need a costly extension. In your custom module, you can easily code functionality by following the above steps. Got error? Feel free to write in the comment box below.

Want to ask a question or leave a comment?

Leave a Comment

(1 Comment)

  • Rajnikant

    Whenever page reloads, the second image source (hover image) get automatically updated with the base image url.

  • All the comments are goes into moderation before approval. Irrelevant comment with links directly goes to spam. Your email address will not be published.

    Was this post helpful? Please support Us!

    Follow us on twitter or Like us on facebook.

     


    To Avoid Spam Downloads, We Want Your Email

    We will send you download link right
    away. Please submit form below.
    SEND ME DOWNLOAD LINK
    Close

    Increase Your
    Magento 2
    Knowledge

    Get Weekly Tutorial
    to your Inbox
    Subscribe Me
    close-link
    Subscribe Here