When you drag a slider with a tag " How can i prevent that?
example:
https://jsfiddle.net/nexuszgt/pgoap3bf/4/
thanks.
@GiancarlosIO
You can achieve the desired effect by using beforeChangeand afterChangemethodsand assign an onClickon the anchors.
Based on your sample code this should be working and will not force links to open on dragging. You can also use ES6 classes instead and bind to thisinstead of a variable.
https://jsfiddle.net/eckrawhy/
@costagolub your "hack" works but i think slick-lib should be fix that because it's a behavior we don't expect
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
has this been fixed?
has this been fixed?
Its not fixed on my 0.23.1 version
Here's my solution that makes use of clientX and clientY attributes of the click and mousedown events and react component state to solve this issue.
Basically it's comparing the mouse position at mouse down and click and if either X or Y is different, then it will preventDefault() (default browser behavior being to go to the link)
class Carousel extends React.Component {
constructor (props) {
super(props)
this.state = {
clientXonMouseDown: null,
clientYonMouseDown: null
}
this.settings = {
dots: true,
arrows: false,
swipeToSlide: true,
infinite: false,
...
}
}
handleOnMouseDown (e) {
this.setState({
clientXonMouseDown: e.clientX,
clientYonMouseDown: e.clientY
})
e.preventDefault() // stops weird link dragging effect
}
handleOnClick (e) {
e.stopPropagation()
if (this.state.clientXonMouseDown !== e.clientX ||
this.state.clientYonMouseDown !== e.clientY) {
// prevent link click if the element was dragged
e.preventDefault()
}
}
render () {
const { items } = this.props
return (
<Slider
{...this.settings} >
{items.map((item, index) => (
<a key={index} href={item.link} target='_blank'
onMouseDown={e => this.handleOnMouseDown(e)}
onClick={e => this.handleOnClick(e)}
>
Link innerText
</a>
))}
</Slider>)
}
}
export default Carousel
Here's my solution that makes use of clientX and clientY attributes of the
clickandmousedownevents and react component state to solve this issue.Basically it's comparing the mouse position at mouse down and click and if either X or Y is different, then it will
preventDefault()(default browser behavior being to go to the link)class Carousel extends React.Component { constructor (props) { super(props) this.state = { clientXonMouseDown: null, clientYonMouseDown: null } this.settings = { dots: true, arrows: false, swipeToSlide: true, infinite: false, ... } } handleOnMouseDown (e) { this.setState({ clientXonMouseDown: e.clientX, clientYonMouseDown: e.clientY }) e.preventDefault() // stops weird link dragging effect } handleOnClick (e) { e.stopPropagation() if (this.state.clientXonMouseDown !== e.clientX || this.state.clientYonMouseDown !== e.clientY) { // prevent link click if the element was dragged e.preventDefault() } } render () { const { items } = this.props return ( <Slider {...this.settings} > {items.map((item, index) => ( <a key={index} href={item.link} target='_blank' onMouseDown={e => this.handleOnMouseDown(e)} onClick={e => this.handleOnClick(e)} > Link innerText </a> ))} </Slider>) } } export default Carouselif we use this logic, it blocks focusOnSelect event which is called on onClick, and the swiping wont be smooth.. that mean it wont fire swipe event too...experience is like a forced swipe .. @fullstackzach
A simple way to bypass click triggering when swipping has already been writted by VeronQ...
But the internal bubbling and native events manager of the lib tiggers the click after the mouseup/touchend.
So the way is to delay a bit the class removing.
// Adds a class when swipping...
$('.slick-slider').off('swipe').on('swipe', function(event, slick, direction){
$('.slick-slider').addClass('swipping');
});
// Remove tha class after swipe was done, AND delayed of ~100 ms
$('.slick-slider').off('afterChange').on('afterChange', function(event, slick, currentSlide, nextSlide){
setTimeout( function(){ $('.slick-slider').removeClass('swipping'); } , 110 );
});
md5-5a823e8887079cfcbffb02e0817ca738
// Your event on the item when the user clicks on.
$('.slick-slider .gp-item .item').off().on('click', function(e){
e.preventDefault();
if( $('.slick-slider').hasClass('swipping') )return;
// Your onclick code here...
});
There is another approach. We could use onClickCapture, onMouseUpCapture, onMouseDownCapture events to prevent click propagation during swiping:
See details here:
https://github.com/akiran/react-slick/issues/604
I'd just like to add something that helped me (not a solution, sorry). There's a setting to disable dragging (draggable: false), but I didn't want to do that because I want mobile users to be able to drag. What I didn't understand is that draggable is only for the mouse. Setting draggable: false still allows touchscreen users to swipe the slider, and this click-after-dragging issue doesn't happen with touchscreens. For desktop users I don't care if they cannot drag the slider with the mouse since I have arrows, so just setting draggable: false prevents this click issue for desktop users, and mobile users can still swipe the carousel without any issues.
Here's my solution that makes use of clientX and clientY attributes of the
clickandmousedownevents and react component state to solve this issue.Basically it's comparing the mouse position at mouse down and click and if either X or Y is different, then it will
preventDefault()(default browser behavior being to go to the link)class Carousel extends React.Component { constructor (props) { super(props) this.state = { clientXonMouseDown: null, clientYonMouseDown: null } this.settings = { dots: true, arrows: false, swipeToSlide: true, infinite: false, ... } } handleOnMouseDown (e) { this.setState({ clientXonMouseDown: e.clientX, clientYonMouseDown: e.clientY }) e.preventDefault() // stops weird link dragging effect } handleOnClick (e) { e.stopPropagation() if (this.state.clientXonMouseDown !== e.clientX || this.state.clientYonMouseDown !== e.clientY) { // prevent link click if the element was dragged e.preventDefault() } } render () { const { items } = this.props return ( <Slider {...this.settings} > {items.map((item, index) => ( <a key={index} href={item.link} target='_blank' onMouseDown={e => this.handleOnMouseDown(e)} onClick={e => this.handleOnClick(e)} > Link innerText </a> ))} </Slider>) } } export default Carousel
I've tried this solution. It works fine for the dragging part. I can drag the items. But when I clicked, there's nothing happened. So I check on what happen in the handleOnClick(), I found that the value of those states are undefined as it didnt mutated immediately after setState in handleOnMouseDown(). Thus it kept on preventDefault. Any idea how to mutate the state immediately or I missed something?
any update on this?
I thought I'd give my two cents and share how I fixed this issue using a React Functional component with Hooks. The key here is to capture the click event using onClickCapture for the slider children (https://javascript.info/bubbling-and-capturing)
import React, { useState, useCallback } from 'react'
import Slick from 'react-slick'
export const Slider = ({children, ...props}) => {
const [dragging, setDragging] = useState(false)
const handleBeforeChange = useCallback(() => {
console.log('handleBeforeChange')
setDragging(true)
}, [setDragging])
const handleAfterChange = useCallback(() => {
console.log('handleAfterChange')
setDragging(false)
}, [setDragging])
const handleOnItemClick = useCallback(
e => {
console.log('handleOnItemClick')
if (dragging) e.stopPropagation()
},
[dragging]
)
return (
<Slick
beforeChange={handleBeforeChange}
afterChange={handleAfterChange}
{...props}
>
{React.Children.map(children, child => (
<div onClickCapture={handleOnItemClick}>{child}</div>
))}
</Slick>
)
}
@fnhipster Thanks for sharing! Unfortunately, that did not work for me. When do you call handleAfterChange and handleBeforeChange?
@MantasMikal my mistake, I missed the handleAfterChange and handleBeforerChange props. I edited my comment https://github.com/akiran/react-slick/issues/848#issuecomment-597185462. I hope that works for you.
@fnhipster that worked for me! Excellent way of solving the issue. Only issue is missing bracket on {handleBeforeChange <--- missing bracket here
Thanks!
You can just add
.slick-slide img {
pointer-events: none;
}
to your css :)
You can just add
.slick-slide img { pointer-events: none; }
to your css :)
That might work only if you don't have any clickable event in your slider. I.e., any link or button would also not be clickable.
I feel like this is a pretty common problem. Any fix on this? My code is not working after trying the above mentioned
import React, { useState, useCallback } from 'react';
import styled from '@emotion/styled';
import Slider from 'react-slick';
import { NoStyleLink } from '../../resusableStyles/Links/LinkStyles';
const Property = styled(NoStyleLink)`
width: 20rem;
height: 20rem;
& img {
width: 100%;
height: 100%;
object-fit: cover;
}
`;
export const PropertyGallery = ({ properties }) => {
const [dragging, setDragging] = useState(false);
const handleBeforeChange = useCallback(() => {
console.log('handleBeforeChange');
setDragging(true);
}, [setDragging]);
const handleAfterChange = useCallback(() => {
console.log('handleAfterChange');
setDragging(false);
}, [setDragging]);
const handleOnItemClick = useCallback(
(e) => {
console.log('handleOnItemClick');
if (dragging) e.stopPropagation();
},
[dragging],
);
const settings = {
dots: true,
infinite: true,
speed: 1000,
autoplaySpeed: 5000,
fadeIn: false,
autoplay: true,
pauseOnHover: true,
slidesToShow: 4,
slidesToScroll: 1,
rows: 1,
responsive: [
{
breakpoint: 1000,
settings: {
slidesToShow: 3,
slidesToScroll: 1,
infinite: true,
rows: 1,
},
},
{
breakpoint: 600,
settings: {
slidesToShow: 2,
slidesToScroll: 1,
},
},
{
breakpoint: 400,
settings: {
slidesToShow: 1,
slidesToScroll: 1,
},
},
],
};
console.log('properties are');
return (
<Slider
beforeChange={handleBeforeChange}
afterChange={handleAfterChange}
{...settings}
>
{properties.slice(0, 5).map((property, i) => (
<Property onClickCapture={handleOnItemClick} key={i}>
<img src={property.acf.mainimage} alt={property.acf.title} />
</Property>
))}
</Slider>
);
};
I like @fnhipster's approach but found that occasionally beforeChange would fire when first mounted but without afterChange also firing. This seems to be related to the issue where initialSlide is not honored sometimes in infinite mode. Probably a deeper timing issue, but at any rate, it meant that sometimes no clicks would register until a swipe event occurred.
I'm getting around that by using the swipe event directly, which as far as I've seen _always_ fires before the child's click event.
import React, { useCallback, useState } from 'react'
import PropTypes from 'prop-types'
import Slick from 'react-slick'
export default ({ children, ...rest }) => {
const [swiped, setSwiped] = useState(false)
const handleSwiped = useCallback(() => {
setSwiped(true)
}, [setSwiped])
const handleOnItemClick = useCallback(
(e) => {
if (swiped) {
e.stopPropagation()
e.preventDefault()
setSwiped(false)
}
},
[swiped],
)
return (
<Slick
onSwipe={handleSwiped}
{...rest}
>
{React.Children.map(children, child => (
<div onClickCapture={handleOnItemClick}>{child}</div>
))}
</Slick>
)
}
HTH
@fnhipster Thanks for your solution! I'm a bit sad that there is no built-in way to prevent clicks on the slider content when the user dragged it (should be possible, shouldn't it?), so I built my own drag detection:
import React, { PropsWithChildren, Ref, useState } from "react";
import Slider, { Settings as SliderProps } from "react-slick";
export type DefaultSliderProps = SliderProps;
/**
* Threshold from which mouse movement with pressed mouse button
* is considered a drag instead of a click.
*/
const MoveDragThreshold = 10;
function useDragDetection(): {
handleMouseDown: () => void;
dragging: boolean;
} {
const [mouseDown, setMouseDown] = useState(false);
const [dragging, setDragging] = useState(false);
useEffect(() => {
let mouseMove = 0;
function handleMouseUp(): void {
setMouseDown(false);
}
function handleMouseMove(e: MouseEvent): void {
mouseMove += Math.abs(e.movementX) + Math.abs(e.movementY);
setDragging(mouseMove > MoveDragThreshold);
}
if (mouseDown) {
document.addEventListener("mouseup", handleMouseUp);
document.addEventListener("mousemove", handleMouseMove);
}
return () => {
document.removeEventListener("mouseup", handleMouseUp);
document.removeEventListener("mousemove", handleMouseMove);
};
}, [mouseDown]);
function handleMouseDown(): void {
setMouseDown(true);
setDragging(false);
}
return {
handleMouseDown,
dragging,
};
}
export function DefaultSlider(
props: PropsWithChildren<DefaultSliderProps>
): React.ReactElement {
const { children, ...sliderProps } = props;
const {
handleMouseDown,
dragging,
} = useDragDetection();
function handleChildClick(
e: React.MouseEvent<HTMLDivElement, MouseEvent>
): void {
if (dragging) {
e.preventDefault();
}
}
return (
<Slider {...sliderProps}>
{React.Children.map(children, (child) => (
<div
onMouseDownCapture={handleMouseDown}
onClickCapture={handleChildClick}
>
{child}
</div>
))}
</Slider>
);
}
The solution from @fnhipster was already quite good, but I didn't like that it still triggered the click when the user only dragged a bit without changing the to another slide. So I introduced the MoveDragThreshold to distinguish between a click and a drag. It might be useful even outside the sliders. Hope this helps some fellow devs! :)
Most helpful comment
I thought I'd give my two cents and share how I fixed this issue using a React Functional component with Hooks. The key here is to capture the click event using
onClickCapturefor the slider children (https://javascript.info/bubbling-and-capturing)