Giter Site home page Giter Site logo

nerdyman / react-compare-slider Goto Github PK

View Code? Open in Web Editor NEW
237.0 2.0 17.0 7.48 MB

A slider component to compare any two React components in landscape or portrait orientation. It supports custom images, videos... and everything else.

Home Page: https://react-compare-slider.vercel.app

License: MIT License

TypeScript 97.93% JavaScript 1.61% Shell 0.46%
react react-component comparison-sliders compare-images compare-image-video typescript portrait twentytwenty comparison compare

react-compare-slider's Introduction

React Compare Slider

Compare two components side-by-side or top-to-toe.

Example

License MIT npm version Bundle size
GitHub CI status Coverage Demos


Features

  • Supports responsive images and any other React components (picture, video, canvas, iframe etc.)
  • Supports landscape and portrait orientations
  • Accessible – screen reader and keyboard support out of the box
  • Simple API
  • Unopinionated & fully customizable – optionally use your own components and styles
  • Responsive & fluid with intrinsic sizing
  • Teeny-tiny, zero dependencies
  • Type safe

Demos

Usage

Install

npm install react-compare-slider
# or
yarn add react-compare-slider
# or
pnpm add react-compare-slider

Basic Image Usage

You may use ReactCompareSliderImage to render images or use your own custom components.

import { ReactCompareSlider, ReactCompareSliderImage } from 'react-compare-slider';

export const Example = () => {
  return (
    <ReactCompareSlider
      itemOne={<ReactCompareSliderImage src="..." srcSet="..." alt="Image one" />}
      itemTwo={<ReactCompareSliderImage src="..." srcSet="..." alt="Image two" />}
    />
  );
};

Props

Prop Type Required Default Description
boundsPadding number 0 Padding to limit the slideable bounds in pixels on the X-axis (landscape) or Y-axis (portrait).
browsingContext globalThis globalThis Context to bind events to (useful for iframes).
clip both|itemOne|itemTwo 5% Percentage or pixel amount to move when the slider handle is focused and keyboard arrow is pressed.
changePositionOnHover boolean false Whether the slider should follow the pointer on hover.
disabled boolean false Whether to disable slider movement (items are still interactable).
handle ReactNode undefined Custom handle component.
itemOne ReactNode undefined First component to show in slider.
itemTwo ReactNode undefined Second component to show in slider.
keyboardIncrement number|`${number}%` 5% Percentage or pixel amount to move when the slider handle is focused and keyboard arrow is pressed.
onlyHandleDraggable boolean false Whether to only change position when handle is interacted with (useful for touch devices).
onPositionChange (position: number) => void undefined Callback on position change, returns current position percentage as argument.
portrait boolean false Whether to use portrait orientation.
position number 50 Initial percentage position of divide (0-100).
transition string undefined Shorthand CSS transition property to apply to handle movement. E.g. .5s ease-in-out

API docs for more information.


Real World Examples

Checkout out the Real World Examples page.

Requirements

  • React 16.8+
  • The latest two versions of each major browser are officially supported (at time of publishing)

react-compare-slider's People

Contributors

dependabot[bot] avatar jsberlanga avatar martin-pettersson avatar moebits avatar nerdyman avatar raphaelts3 avatar zoltan-mihalyi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

react-compare-slider's Issues

Only fire movement event on mouse left down

Movement is currently triggered on any mouse button, it should only fire on left click to allow right click to work as normal without the slider sticking to the mouse.

Although it's unlikely anyone was relying on it also working on right click, this commit should be marked as a BREAKING CHANGE.

Unhandled Runtime Error ReferenceError: Can't find variable: ResizeObserver on Safari

Hello,

I installed and can use this on Firefox, but it breaks on Safari, both Desktop and iPhone versions.

the error is (when run from localhost:3000)

Unhandled Runtime Error
ReferenceError: Can't find variable: ResizeObserver

I installed this to fix, but it didn't help
import ResizeObserver from 'resize-observer-polyfill';

When google this, it looks like many sliders have this issue.

I see that this issue has been closed for gatsby. My app doesn't use gatsby. I tried adding import ResizeObserver from 'resize-observer-polyfill'; and after installing it with no luck

this project uses Next.js

Add Live Demos

Add live demos.

  • Add CodeSandbox examples
  • Add Storybook integration
  • Auto-deploy to netlify on push

Make component completely controllable from the parent

The slider currently takes props like position and moves them to internal states. This is moslty fine but leads to edge cases as shown in #64 in #48.

Option 1 - Standalone Hook

The state for the main component could be abstracted into it's own hook and exported from the library. The slider component itself would not call the hook, the parent component would instead. This would allow the parent component to manipulate all state and share it with ReactCompareSlider.

E.g.

import { ReactCompareSlider, useSliderState } from 'react-compare-slider';

export const Example = () => {
  const sliderState = useSliderState();

    /** Optional override */
    const onPositionChange = (position) => {
      window.alert(position);
      sliderState.setPosition(position);
    };

  return <ReactCompareSlider {...sliderState} onPositionChange={onPositionChange} itemOne={} itemTwo={} />
};

Option 2 - useimperativehandle

useimperativehandle may be a simpler option from a DX perspective as the API for v2 and v3 would remain the same but you could optionally call the hook to access the internal properties of the slider.

Add `transition` functionality for fancy animations ✨

  • Should use standard CSS transition properties to transition slider position on pointer down and key arrow press
  • Should move immediately to pointer position if pointer down + drag occurs
  • Add prop to README props table
  • Add Storybook story
  • Add Storybook tests

See #67 for prior work.

bug: TypeError: Cannot read properties of null (Refs Issue)

Bug: Both clipContainerRef and handleContainerRef's current prop will output to null.

If a user were to refresh the page, the default null value will be set. As a result, it will display this TypeError that I've screenshotted below. I've tried this using the codepen code link.

DwellWell

proposed solution:
add the following conditionals to for the refs in ReactCompareSlider.tsx

if (clipContainerRef && clipContainerRef.current) {
  (clipContainerRef.current as HTMLElement).style.clip = _portrait
    ? `rect(auto,auto,${clampedPx}px,auto)`
    : `rect(auto,${clampedPx}px,auto,auto)`;
}
if (handleContainerRef && handleContainerRef.current) {
  (handleContainerRef.current as HTMLElement).style.transform = _portrait
    ? `translate3d(0,${clampedPx}px,0)`
    : `translate3d(${clampedPx}px,0,0)`;
}

v3.0.1 compatibility with React 17.0.2 (Reproducible)

Hey! Seems like the react-compare-slider isn't compatible with react 17.0.2.

Error:

Module not found: Error: Can't resolve 'react/jsx-runtime' in '.../react-compare-slider/dist'
Did you mean 'jsx-runtime.js'?
BREAKING CHANGE: The request 'react/jsx-runtime' failed to resolve only because it was resolved as fully specified
(probably because the origin is strict EcmaScript Module, e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '"type": "module"').
The extension in the request is mandatory for it to be fully specified.
Add the extension to the request.

Here's the code to the issue:
https://github.com/SalahHamza/react-compare-slider-repro

Replace touch and mouse events with pointer events

The library currently uses mouse and touch event bindings from V1 when IE11 was supported. All currently targeted browsers support pointer events so they should be used instead.

See 5c84c5c (full file here) for a reference implementation.

Requirements

  • Add touchAction: 'none' style property to root element
  • Replace mousedown event binding with pointerdown
  • Remove touchstart event binding
  • Update mouse and touch handlers to replace MouseEvent | TouchEvent with PointerEvent
  • Remove instanceof ternaries in mouse and touch handlers, we can use ev.pageX and ev.pageY

Testing

  • Test on iOS Safari
  • Test on iOS Chrome
  • Test on Firefox Android
  • Test on Android Chrome
  • Test on Desktop Safari
  • Test on Desktop Chrome
  • Test on Desktop Firefox

TODO

bug when position is initially set to either 0 or 100

I can replicate both these issues using the example in this GH repo.

If you initially set position to 100, the handle is placed on the exact opposite side to what you'd expect.

Whereas if you set position to 0 the handle is in the correct position but itemOne is being displayed when it should be hidden.

Here's a screenshot of it set up with 100:
Screenshot 2021-05-27 at 17 52 26

In the example, it seems to be slightly specific to the size of the browser viewport i.e. if I have my browser maximised it works fine for position: 100 but still has an issue with position: 0.

I did a bit of debugging and the problem seems to lie around here:

/** Determine if the current pixel position meets the min/max bounds. */
const positionMeetsBounds = _portrait
? positionPx === 0 || positionPx === height
: positionPx === 0 || positionPx === width;
const canSkipPositionPc =
nextInternalPositionPc === internalPositionPc.current &&
(internalPositionPc.current === 0 || internalPositionPc.current === 100);
// Early out if pixel and percentage positions are already at the min/max
// to prevent update spamming when the user is sliding outside of the
// container.
if (canSkipPositionPc && positionMeetsBounds) {
return;
}

Specifically that on some occasions it's returning on L197 when, under normal circumstances (e.g. position being set to 99) it would not.

One workaround I've found is to set the initial position as a String rather than a Number

"ReferenceError: document is not defined" in SSR

Hello, and thank you for your work on this component!

I get a "document is not defined" error on the server when I am using server side rendering.

It looks like the problem is here:

const containerRef = useRef<HTMLDivElement>(document.createElement('div'));

I tried to fork the project an replace
const containerRef = useRef<HTMLDivElement>(document.createElement('div'));
with
const containerRef = useRef<HTMLDivElement>(null);
and add null-checks whenever containerRef.current is used, but my experience with typescript is limited and I didn't want to make a mess! 😀

Video Does not Autoplay

I tried to add two videos to compare, which both have the autoplay and muted tags. However, the videos do not autoplay. I can add the controls tag and manually click the play button, but this is not desireable.

Synchronizing two sliders - is it possible?

Hi!
I want to implement next feature - while dragging the handler of the first slider the other slider changes simultaneously according to motion of the first one
Is it possible to embody?

Upgrade to Node 18 LTS

  • Update .nvmrc to use Node 18 LTS
  • Update actions to use node set up action
  • Update actions to use .nvmrc
  • Upgrade Storybook to use Webpack 5 (Webpack 4 is not compatible with SSL options in Node 18)

ResizeObserver is not defined (Gatsby)

I've used react-compare-slider for a Gatsby project, and to my surprise the build kept failing because of

WebpackError: ReferenceError: ResizeObserver is not defined
  
  - react-compare-slider.esm.js:149 
    node_modules/react-compare-slider/dist/react-compare-slider.esm.js:149:26
  
  - react-compare-slider.esm.js:395 
    node_modules/react-compare-slider/dist/react-compare-slider.esm.js:395:1

The offending lines being:

const useResizeObserver = (ref, handler) => {
> 149 |   const observer = useRef(new ResizeObserver(([entry]) => handler(entry.contentRect)));
      |                          ^
  150 |   const observe = useCallback(() => {
  151 |     ref.current && observer.current.observe(ref.current);
  152 |   }, [ref]); // Bind/rebind observer when `handler` changes.


  WebpackError: ReferenceError: ResizeObserver is not defined

I've tried it all unsuccessfully polyfilling ResizeObserver in my Gatsby project, but the one thing that made it work was to

npm install resize-observer-polyfill --save

And then add

import ResizeObserver from 'resize-observer-polyfill';

on line 2 of node_modules/react-compare-slider/dist/react-compare-slider.esm.js. That made it compile the website without errors.

I have no idea whether there were better ways to solve this directly in Gatsby without modifying your package (I haven't found them, but if anyone has ideas I'll be glad to try), I'm mentioning this in case somebody runs into the same issue.

Thanks!

Implement comprehensive tests

Implement comprehensive automated tests for the entire lib.

ReactCompareSlider

  • Ensure clip-path value matches expected output (currently only testing for clip)
  • Ensure onPositionChange value matches expected output
  • Ensure portrait orientation matches expected position
  • Ensure custom handle is rendered in DOM

ReactCompareSliderImage

  • Ensure fallback is used when object-fit is not supported

useLayoutEffect warning

Hi I'm getting this error when using react-compare-slider on my nextjs project :

useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client.

v3.0.1 compatibility with React < v18.0.0

Is v3.0.1 compatible with React v17.0.2?

The docs state React v16.8+ is required, but I get the following server error.

Code

import { ReactCompareSlider, ReactCompareSliderImage } from 'react-compare-slider';

export const Example = () => {
  return (
    <ReactCompareSlider
      itemOne={<ReactCompareSliderImage src="..." srcSet="..." alt="Image one" />}
      itemTwo={<ReactCompareSliderImage src="..." srcSet="..." alt="Image two" />}
    />
  );
};

Server Error

Error: Cannot find module '.../node_modules/react/jsx-runtime' imported from ...node_modules/react-compare-slider/dist/index.mjs
Did you mean to import react/jsx-runtime.js?

Versions

"react": "^17.0.2",
"react-compare-slider": "^3.0.1",
"react-dom": "^17.0.2",

Worth noting, when reverting to v2.2.0, it works with React v17.0.2.

Try using percentage units internally instead of percentage and pixel mix

Since we're now using clip-path we can use % units instead of pixels.

This may remove the need for the ResizeObverser in the main component as the percentage value will still be the same even when the component resizes. It should also should hopefully resolve the Safari keyboard increment issue from #69 and the Safari console error from #103.

^ The above is probably not possible because boundsPadding is pixel based, so it needs to be recalcuated on resize relative to the root container.

Considerations:

  • boundsPadding and CSS scaling should still be respected

Scroll issue on mobile devices after "touch-action: none" styling

Hello everyone, I wanted to bring to your attention an issue that arose in one of the commits early in 2023 (Commit Link).

In this commit, the widget received the style property "touch-action: none", resulting in the disruption of scrolling on relevant devices. This has become a critical issue, especially on mobile phones, where the widget might occupy a significant portion of the screen.

Handle position when used in RTL content is way off (landscape only)

In landscape mode, something goes very wrong with the handle display when this is used in a web page (or element) that has dir="rtl" set on on it... it seems to be displaying on the far right of the .handle-container div.

I've set up a demo here: https://codesandbox.io/s/react-compare-slider-simple-example-forked-u6wlmp

From what I can see if you remove position:absolute from the div that's the immediate child of it, that fixes it:
image

role="slider" requires accessible name (aria-label)

Hi

How is this aria-label <div aria-label="Drag to move">, that's nested inside the slider button, announced?

<button role="slider">
  <div aria-label="Drag to move">
  ...
</button>

I'm looking for a solution to add a custom aria announcement when interacting with the slider button. But looking at the HTML, the only aria-label I see is nested and not actually reached (from testing with VO on Safari/Chrome).

Looking at the MDN docs for the role=slider, it mentions

An accessible name is required. If the range's role is applied to an HTML element (or or element), the accessible name can come from the associated . Otherwise use aria-labelledby if a visible label is present or aria-label if a visible label is not present.

also

Accessibility APIs do not have a way of representing semantic elements contained in a slider. To deal with this limitation, browsers, automatically apply role presentation to all descendant elements of any slider element as it is a role that does not support semantic children.

Reference: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/slider_role

Thanks.

Replace yarn with pnpm

  • Replace yarn with pnpm
  • Use Corepack and packageManager property in package.json to set version
  • Make example project a workspace so dependencies are automatically installed

How to make draggable area to be on the handle only?

First of all this is very good library but I have one UX problem in mind.

Specially when using with mobile if you have list of different ReactCompareSliders which take full width of the mobile screen it is annoying to scroll list vertically because you accidentally start sliding the ReactCompareSlider even though you wanted to scroll content of the page vertically.

So would be nice to restrtict the area of ReactCompareSlider so that only if user drags the handle then it starts to drag ReactCompareSliders content horizontally. What do you guys think?

Add support for images with different sizes

Hey, I'm trying to use this lib, which looks pretty good, although I'm missing a small feature

I was using react-compare-image before, and it has this aspectRatio prop feature, allowing for the slider to look fine when images aspect ratios differ, and therefore being able to view both images in full size.

Shot 2024-01-29 at 12 09 01@2x

Here's their examples:
https://react-compare-image-git-master-junkboy0315.vercel.app/?path=/story/images-with-different-aspect-ratios--default-behavior

I created a sandbox showing the current behavior

https://codesandbox.io/p/devbox/infallible-shape-t5p8tl?file=/src/App.tsx:16,10

Would it be possible for you to add such feature? Or is it already possible? And if it is, how to set it up?

Thanks in advance!

Limit the focus of the handle element

Hello! I am facing the following situation: I have a page with a vertical slide using your library. Suppose I can't see the slide completely, but I can move the handle. In this case, the page will scroll to the element because of the focus (https://github.com/nerdyman/react-compare-slider/blob/main/lib/src/ReactCompareSlider.tsx#L192-L195)

I would like to be able to disable this behavior. For example, the possibility to pass a boolean flag that will default to 'true' to keep the current behavior. Thanks!

Before clicking

Снимок экрана 2024-01-30 в 16 32 45

After just clicking to the handle

Снимок экрана 2024-01-30 в 16 33 11

A11y support

Hello. Are you planning to add accessibility support (keyboard navigation and aria attributes for screen readers)?
Right now it's impossible to move the handle using the keyboard, and there is no way to know current position of the handle if you're using a screen reader.
Also as the items are rendered in reverse order (item 2 - item 1) if you're using a screen reader it will read the content in a reverse order as well (first the content of the right block and then the content of the left block).

How do differently sized images scale in sync?

Hello. Thanks for staring ⭐ Creevey. I see you made nice looking react compare slider. In Creevey I made something similar in SlideView, but less powerful and based on native input[type=range] with a few styles. Could you tell how your implementation handle images with different sizes?

lighthouse fail of images

hello,

I love this plugin but it is causing me to fail with google page speed insights in a few spots.

Next.js has lazyloading built in if I use its < Image /> component which helps with all my other images.

is there a way to rewrite the following to use that?



	<ReactCompareSlider
  itemOne={<ReactCompareSliderImage src="https://res.cloudinary.com/djk8wzpz4/image/upload/v1611180870/MCA-pixel-portrait_jqm50p.jpg" srcSet="https://res.cloudinary.com/djk8wzpz4/image/upload/v1611180870/MCA-pixel-portrait_jqm50p.jpg" alt="Image one" />} 

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.