Search code examples
javascriptcssreactjscarouselreact-responsive-carousel

Using react-responsive-carousel with react-image-magnifiers


I am facing issues using react-responsive-carousel with react-image-magnifiers

I am trying to use carousel over react-image-magnifier library.

I do not get the magnifier effect when using with carousel but when I use react-image-magnifier alone I get the effect.

please find codesandbox link

import { Carousel } from "react-responsive-carousel";
import { SideBySideMagnifier } from "react-image-magnifiers";
import "react-responsive-carousel/lib/styles/carousel.min.css";

export default function ProductSlider() {
  const renderCustomThumbs = () => {
    return [
      <picture>
        <source
          data-srcSet="https://i.ibb.co/z5CC6P9/AB-10000008011.jpg"
          type="image/jpg"
        />
        <img
          key="01"
          src="https://i.ibb.co/z5CC6P9/AB-10000008011.jpg"
          alt="First Thumbnail"
          height="70"
        />
      </picture>,
      <picture>
        <source
          data-srcSet="https://i.ibb.co/z5CC6P9/AB-10000008011.jpg"
          type="image/jpg"
        />
        <img
          key="02"
          src="https://i.ibb.co/z5CC6P9/AB-10000008011.jpg"
          alt="Second Thumbnail"
          height="70"
        />
      </picture>
    ];
  };

  return (
    <>
      <h2>With Carousel magnifiers dosen't works </h2>
      <div style={{ width: "50%" }}>
        <Carousel
          showArrows={false}
          showStatus={true}
          showIndicators={false}
          showThumbs={true}
          autoPlay={false}
          transitionTime={500}
          swipeable={false}
          emulateTouch={true}
          renderThumbs={renderCustomThumbs}
        >
          <div>
            <SideBySideMagnifier
              imageSrc="https://i.ibb.co/z5CC6P9/AB-10000008011.jpg"
              imageAlt="First Slide"
              alwaysInPlace={false}
              fillAvailableSpace={true}
              fillAlignTop={true}
              fillGapRight={10}
              fillGapBottom={10}
              fillGapTop={10}
              fillGapLeft={0}
            />
          </div>
          <div>
            <SideBySideMagnifier
              imageSrc="https://i.ibb.co/z5CC6P9/AB-10000008011.jpg"
              imageAlt="Second Slide"
              alwaysInPlace={false}
              fillAvailableSpace={true}
              fillAlignTop={true}
              fillGapRight={10}
              fillGapBottom={10}
              fillGapTop={10}
              fillGapLeft={0}
            />
          </div>
          )
        </Carousel>
      </div>
      <h2>Without Carousel magnifiers works </h2>
      <div style={{ width: "50%" }}>
        <SideBySideMagnifier
          imageSrc={"https://i.ibb.co/z5CC6P9/AB-10000008011.jpg"}
          alwaysInPlace={false}
          fillAvailableSpace={true}
          fillAlignTop={true}
          fillGapRight={10}
          fillGapBottom={10}
          fillGapTop={10}
          fillGapLeft={0}
        />
      </div>
    </>
  );
}



Solution

  • Why the Magnifier isn't showing

    The Magnifiers are not showing because the Carousel wrapper is clipping them. The Magnifiers are drawn within each Carousel slide and to the right of each image, but because they're styled with position: absolute, they aren't included in calculations involving the slide width. The Carousel has overflow: hidden set so that all content wider than the current slide is not visible; this is why it clips off the Magnifiers.

    Rebuilding the Magnifiers

    Fortunately the docs for react-image-magnifiers indicate that you can get around this by setting up your own layout with the Magnifier outside of the Carousel. You are currently importing the SideBySideMagnifier component which automatically sets up both the normal and magnified images. Instead we'll use the following custom layout components:

    import { MagnifierContainer, MagnifierPreview, MagnifierZoom } from "react-image-magnifiers";
    

    You can have the normal image (MagnifierPreview) and zoomed image (MagnifierZoom) in any hierarchy you want - the only constraint is that they must both be children of the MagnifierContainer component. So we'll wrap the entire carousel in a MagnifierContainer component.

    We want to show the MagnifierZoom alongside the carousel. Therefore we'll need to set up two wrapper divs side by side. We can do that with a bit of CSS.

    <MagnifierContainer>
        <div className="magnifier-content">
            <div className="magnifier-carousel">
                <!-- this will contain the carousel -->
            </div>
            <div className="magnifier-zoom">
                <!-- this will contain the zoomed images -->
            </div>
        </div>
    </MagnifierContainer>
    
    .magnifier-content {
        display: flex;
    }
    .magnifier-content > div {
        width: 50%;
    }
    

    Now, each image (MagnifierPreview) will be connected to its zoomed component (MagnifierZoom) if they have the same imageSrc property. That is, for every MagnifierPreview we have, we will also need a MagnifierZoom component.

    The JSX for the carousel looks pretty much like your current code. We'll need to add each zoom element in as well.

    Carousel code goes in the magnifier-carousel element:

    <div className="magnifier-carousel">
        <Carousel
            ...
        >
            <div>
                <MagnifierPreview
                    imageSrc="https://i.ibb.co/z5CC6P9/AB-10000008011.jpg"
                    imageAlt="First Slide"
                    ...
                />
            </div>
            <div>
                <MagnifierPreview
                    imageSrc="https://i.ibb.co/z5CC6P9/AB-10000008011.jpg"
                    imageAlt="Second Slide"
                    ...
                />
            </div>
        </Carousel>
    </div>
    

    Zoom element code codes in the .magnifier-zoom element:

    <div className="magnifier-zoom">
        <MagnifierZoom style={{ height: "400px" }} imageSrc="https://i.ibb.co/z5CC6P9/AB-10000008011.jpg"/>
        <MagnifierZoom style={{ height: "400px" }} imageSrc="https://i.ibb.co/z5CC6P9/AB-10000008011.jpg"/>
    </div>
    

    Only showing a single Magnifier

    If you were to run this, you'd find that it works, but the MagnifierZoom components are both showing. We want to only show the one currently being viewed. To do this we need to set their position to be absolute, so that we can give them the correct coordinates in their container, and then use a function to hide/show the MagnifierZooms whenever the carousel changes.

    The CSS to position them, and to hide them except the first one on page load:

    .magnifier-zoom {
      position: relative;
    }
    .magnifier-zoom div {
      position: absolute!important;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      opacity: 0;
    }
    .magnifier-zoom div:first-child {
      opacity: 1;
    }
    

    The function to change which one is visible when the carousel slide changes:

    const showMagnifier = ( current_index ) => {
        document.querySelectorAll('.magnifier-zoom div').forEach(( el, image_index ) => {
            if ( current_index === image_index ) {
                el.style.opacity = '1';
            } else {
                el.style.opaccity = '0'
            }
        })
    }
    

    We can now add an onChange property to the Carousel component:

    <Carousel
        onChange={showMagnifier}
        ...
    >
    

    And this should be working fine!

    Complete solution

    The complete code for your App.js and styles.css:

    App.js

    import { Carousel } from "react-responsive-carousel";
    import { MagnifierContainer, MagnifierPreview, MagnifierZoom, SideBySideMagnifier } from "react-image-magnifiers";
    import "react-responsive-carousel/lib/styles/carousel.min.css";
    import "./styles.css";
    export default function ProductSlider() {
      const renderCustomThumbs = () => {
        return [
          <picture>
            <source
              data-srcSet="https://i.ibb.co/z5CC6P9/AB-10000008011.jpg"
              type="image/jpg"
            />
            <img
              key="01"
              src="https://i.ibb.co/z5CC6P9/AB-10000008011.jpg"
              alt="First Thumbnail"
              height="70"
            />
          </picture>,
          <picture>
            <source
              data-srcSet="https://i.ibb.co/z5CC6P9/AB-10000008011.jpg"
              type="image/jpg"
            />
            <img
              key="02"
              src="https://i.ibb.co/z5CC6P9/AB-10000008011.jpg"
              alt="Second Thumbnail"
              height="70"
            />
          </picture>
        ];
      };
    
      const showMagnifier = ( current_index ) => {
        document.querySelectorAll('.magnifier-zoom div').forEach(( el, image_index ) => {
          if ( current_index === image_index ) {
            el.style.opacity = '1';
          } else {
            el.style.opaccity = '0'
          }
        })
      }
    
      return (
        <>
          <MagnifierContainer>
          <div className="magnifier-content">
            <div className="magnifier-carousel">
                <Carousel
                  showArrows={false}
                  showStatus={true}
                  showIndicators={true}
                  showThumbs={true}
                  autoPlay={false}
                  transitionTime={500}
                  swipeable={false}
                  emulateTouch={true}
                  renderThumbs={renderCustomThumbs}
                  onChange={showMagnifier}
                >
                  <div>
                    <MagnifierPreview
                      imageSrc="https://i.ibb.co/z5CC6P9/AB-10000008011.jpg"
                      imageAlt="First Slide"
                      alwaysInPlace={false}
                      fillAvailableSpace={true}
                      fillAlignTop={true}
                      fillGapRight={10}
                      fillGapBottom={10}
                      fillGapTop={10}
                      fillGapLeft={0}
                    />
                  </div>
                  <div>
                    <MagnifierPreview
                      imageSrc="https://i.ibb.co/z5CC6P9/AB-10000008011.jpg"
                      imageAlt="Second Slide"
                      alwaysInPlace={false}
                      fillAvailableSpace={true}
                      fillAlignTop={true}
                      fillGapRight={10}
                      fillGapBottom={10}
                      fillGapTop={10}
                      fillGapLeft={0}
                    />
                  </div>
                </Carousel>
              </div>
              <div className="magnifier-zoom">
                <MagnifierZoom style={{ height: "400px" }} imageSrc="https://i.ibb.co/z5CC6P9/AB-10000008011.jpg"/>
                <MagnifierZoom style={{ height: "400px" }} imageSrc="https://i.ibb.co/z5CC6P9/AB-10000008011.jpg"/>
              </div>
            </div>
          </MagnifierContainer>
        </>
      );
    }
    

    styles.css

    .App {
      font-family: sans-serif;
      text-align: center;
    }
    .magnifier-content {
      display: flex;
    }
    .magnifier-content > div {
      width: 50%;
    }
    .magnifier-zoom {
      position: relative;
    }
    .magnifier-zoom div {
      position: absolute!important;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      opacity: 0;
    }
    .magnifier-zoom div:first-child {
      opacity: 1;
    }