Touchevents not firing on IOS Safari in React custom carousel component after first swipe - ios

I created a custom photo carousel component in React (because external libraries were either too hard to work with or did not do the things that I wanted it to do), where you can swipe to the next/previous photo on mobile. Everything works fine on Android, but it's just IOS Safari.
The Problem
I have a page that maps out several carousels. The first carousel in the map works perfectly fine. Subsequent carousels will swipe correctly AFTER the first slide, but once it transitions to the second slide, the touch events stop firing, and it will not swipe. What I want is all the carousels like the first carousel. No error messages seen either. See video:
Code
Here is the custom component:
import { useState, useEffect } from 'react'
const Carousel = ({ children }) => {
const IMG_WIDTH = 400
const [currentIndex, setCurrentIndex] = useState(0)
const [lastTouch, setLastTouch] = useState(0)
const [movement, setMovement] = useState(0)
const [transitionDuration, setTransitionDuration] = useState('')
const [transitionTimeout, setTransitionTimeout] = useState(null)
const maxLength = children.length - 1,
maxMovement = maxLength * IMG_WIDTH
useEffect(() => {
return () => {
clearTimeout(transitionTimeout)
}
}, [])
const transitionTo = (index, duration) => {
setCurrentIndex(index)
setMovement(index * IMG_WIDTH)
setTransitionDuration(`${duration}s`)
setTransitionTimeout(
setTimeout(() => {
setTransitionDuration('0s')
}, duration * 100))
}
const handleMovementEnd = () => {
const endPosition = movement / IMG_WIDTH
const endPartial = endPosition % 1
const endingIndex = endPosition - endPartial
const deltaInteger = endingIndex - currentIndex
let nextIndex = endingIndex
if (deltaInteger >= 0) {
if (endPartial >= 0.1) {
nextIndex++
}
} else if (deltaInteger < 0) {
nextIndex = currentIndex - Math.abs(deltaInteger)
if (endPartial > 0.9) {
nextIndex++
}
}
transitionTo(nextIndex, Math.min(0.5, 1 - Math.abs(endPartial)))
}
const handleMovement = delta => {
clearTimeout(transitionTimeout)
const maxLength = children.length - 1
let nextMovement = movement + delta
if (nextMovement < 0) {
nextMovement = 0
}
if (nextMovement > maxLength * IMG_WIDTH) {
nextMovement = maxLength * IMG_WIDTH
}
setMovement(nextMovement)
setTransitionDuration('0s')
}
const handleTouchStart = event => {
setLastTouch(event.nativeEvent.touches[0].clientX)
}
const handleTouchMove = event => {
const delta = lastTouch - event.nativeEvent.touches[0].clientX
setLastTouch(event.nativeEvent.touches[0].clientX)
handleMovement(delta)
}
const handleTouchEnd = () => {
handleMovementEnd()
setLastTouch(0)
}
return (
<div
className="main"
style={{ width: IMG_WIDTH }}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}>
<div
className="swiper"
style={{
transform: `translateX(${movement * -1}px)`,
transitionDuration: transitionDuration
}}>
{children} // This is just <img /> tags
</div>
<div className="bullets">
{[...Array(children.length)].map((bullet, index) => (
<div key={`bullet-${index}`} className={`dot ${currentIndex === index && 'red-dot'}`} />
))}
</div>
</div>
)
}
export default Carousel
And here is the part of my code where I am using the custom component:
return (
<>
<Announcement />
<Header />
<section className="tiles-section">
<Title />
{Component} // This is just a component that simply maps out the Carousel, nothing special and no extra styling
</section>
...
</>
)
And CSS:
.main {
overflow: hidden;
position: relative;
touch-action: pan-y;
}
.swiper {
display: flex;
overflow-x: visible;
transition-property: transform;
will-change: transform;
}
What I know/tried
Removing some of the components above the Carousel (eg. Title, Announcement, etc.) makes part of the slides other than the first slide swipable. It can be half of the height of the slide or 1/3 of the height that is swipable, depending on how much I remove. This excludes the first carousel, that one still works perfectly fine
I've tried removing a bunch of CSS, didn't work
Tried adding event listeners to the 'main' div, maybe I did something wrong but swiping one carousel ends up swiping all the carousels
Thought it had something to do with the touch event handler functions I created in my custom carousel component, but it seems like they work fine, because after changing them to console.logs and manually translating the slides, the touch events are still not firing.
Update #1
I put the touch event handlers in a new div that wraps {child}. The second slide is swipeable now but the third slide is still a bust.
References
This is the tutorial link I followed to create the carousel for more context to the custom carousel component.

After days and hours, the solution is kind of odd. I'm guessing you need to set the target correctly to the element's div id. I put this in the useEffect, so the result looks like this:
useEffect(() => {
document.getElementById(id).addEventListener('touchstart', () => false, { passive: false })
return () => {
clearTimeout(transitionTimeout)
}
}, [])
NOTE that the id MUST be unique especially if creating a map of the same component like what I did. Otherwise, swiping one carousel will swipe all carousels.

Related

Intersection Observer API not working in ios device when opening a webview in it but working perfectly in web, android , safari browser

`Below mentioned is my code for intersection observer
useIntersectionObserver
/* eslint-disable #typescript-eslint/no-shadow */
import { RefObject, useEffect, useState } from 'react';
export default function useIntersectionObserver(
elementRef: RefObject<Element>,
{ threshold = 0, root = null, rootMargin = '0%' }
) {
const [entry, setEntry] = useState<IntersectionObserverEntry>();
const callBackFunction = ([entry]: IntersectionObserverEntry[]): void => {
setEntry(entry);
};
useEffect(() => {
const node = elementRef?.current; // DOM Ref
const isIOSupported = !!window.IntersectionObserver;
if (!isIOSupported || !node) return;
const observerOptions = { threshold, root, rootMargin };
const observer = new IntersectionObserver(
callBackFunction,
observerOptions
);
observer.observe(node);
return () => observer.disconnect();`your text`
}, [elementRef, JSON.stringify(threshold), root, rootMargin]);
return entry;
}
Below is the code mentioned where I am calling useInterSectionObserver hook
const scrollDivElementRef = useRef<null | HTMLDivElement>(null);
const chatDivRef = useRef<null | HTMLDivElement>(null);
const entry = useIntersectionObserver(scrollDivElementRef, {});
const isVisible = !!entry?.isIntersecting;
Here scrollDivElementRef is the ref of div which we are observing for the intersection.It is basically our sentinal element.
Below mentioned is the useEffect hook which is going to perform some action when isVisible will become true.
Below mentioned is a code in react-native webview ,basically we are opening our react web app inside ios app , but our intersection observer is not able to detect changes . We have implemented intersection observer for infinite loading of messages. Every time user will scroll upwards , we will get "isVisible" as true and we will make an API call to fetch more messages.
useEffect(() => {
if (isVisible) {
dispatch(
getPostsForChannel(inputChannelId, true, channelMessages[0].timestamp)
);
}
}, [isVisible]);
<View style={styles.container}>
<WebView
style={{ marginTop: 40 }}
//TODO: Update the url
source={{ uri: 'http://192.168.1.2:3000' }}
onLoad={(syntheticEvent) => {
const { nativeEvent } = syntheticEvent;
}}
javaScriptEnabled={true}
onError={(e) => {
const { nativeEvent } = e;
setUrl(HOME_URL);
}}
onHttpError={() => {
setUrl(HOME_URL);
}}
onMessage={onMessage}
limitsNavigationsToAppBoundDomains={true}
// injectedJavaScript="window.octNativeApp=true"
injectedJavaScriptBeforeContentLoaded={initializeNativeApp}
scalesPageToFit={false}
setBuiltInZoomControls={false}
useWebView2
allowsInlineMediaPlayback={true}
mediaPlaybackRequiresUserAction={false}
></WebView>
</View>`
It will be really very helpful , if you people can help me with this problem.
Thanks & Regards
Mohit
I tried giving the height & width to the webview but it is also not working.Tried almost every piece of advise available in other platforms but not able to fic this`

How to make a GUI to visually add Mattertags into a Matterport scene?

There are 2 examples in the Matterport SDK for Embeds documentation to show how to place Mattertags in a scene:
The Intersection Inspector which only allows you to see coordinates for placing a Mattertag where the cursor is if you wait a little bit ... Not very user friendly, you need to copy the coordinates manually in your program.
The Transient Tags Editor which enable you to interactively place multiple Mattertags visually, edit them and then to extract them easily in a JSON file ...
I was wondering how to reproduce the Transient Tags Editor visual UX as I would like to use it in an application.
Insert Mattertags into the model visually
The source code of the app of the Transient Tags Editor is privately hosted on github (Maybe because it doesn't run perfectly on Firefox?), unlike the source code of the Intersection Inspector which is publicly hosted on JSFiddle.
But the user friendliness of the Transient Tags Editor intrigued me and I wanted to understand the difference between the two tools Matterport SDK provides to find out Mattertags coordinates.
How the Intersection Inspector works
The Intersection Inspector uses a timer to display a button at the position of the Pointer when the user does not move the pointer for more than one second. The user can then click the button to see the Mattertag coordinates and copy them manually ...
To achieve that, it needs the current Camera position, which it obtains by observing the camera's pose property:
var poseCache;
mpSdk.Camera.pose.subscribe(function(pose) {
poseCache = pose;
});
Also, it needs the current Pointer position, which it obtains by observing the pointer's intersection property:
var intersectionCache;
mpSdk.Pointer.intersection.subscribe(function(intersection) {
intersectionCache = intersection;
intersectionCache.time = new Date().getTime();
button.style.display = 'none';
buttonDisplayed = false;
});
※ An intersection event is triggered the user moves the pointer, so we hide the button to make sure it is not displayed before the one second delay is over.
Then, a timer is set up using setInterval() to display the button at the right time:
setInterval(() => {
// ...
}, 16);
In the timer callback, we check wether all the conditions to display the button are met ...
First, check we have the information we need:
setInterval(() => {
if (!intersectionCache || !poseCache) {
return;
}
// ...
}, 16);
Then, check one second has elapsed since the last intersection event was received, or we wait the next tick to check again:
var delayBeforeShow = 1000;
setInterval(() => {
if (!intersectionCache || !poseCache) {
return;
}
const nextShow = intersectionCache.time + delayBeforeShow;
if (new Date().getTime() > nextShow) {
// ...
}
}, 16);
Finally, we check the button is not already being displayed:
var delayBeforeShow = 1000;
var buttonDisplayed = false;
setInterval(() => {
if (!intersectionCache || !poseCache) {
return;
}
const nextShow = intersectionCache.time + delayBeforeShow;
if (new Date().getTime() > nextShow) {
if (buttonDisplayed) {
return;
}
// ...
}
}, 16);
Once the conditions are met, we can display the button using Conversion.worldToScreen() to get the screen coordinate of the pointer :
// ...
setInterval(() => {
// ...
if (new Date().getTime() > nextShow) {
// ...
var size = {
w: iframe.clientWidth,
h: iframe.clientHeight,
};
var coord = mpSdk.Conversion.worldToScreen(intersectionCache.position, poseCache, size);
button.style.left = `${coord.x - 25}px`;
button.style.top = `${coord.y - 22}px`;
button.style.display = 'block';
buttonDisplayed = true;
}
}, 16);
The button is a simple HTML button hidden by default using display: none; and positioned relative to the body with position: absolute;.
When the user clicks the button, the current coordinates of the pointer are displayed in a <div> tag above the <iframe> and the button is hidden:
button.addEventListener('click', function() {
text.innerHTML = `position: ${pointToString(intersectionCache.position)}\nnormal: ${pointToString(intersectionCache.normal)}\nfloorId: ${intersectionCache.floorId}`;
button.style.display = 'none';
iframe.focus();
});
The coordinates are formatted using the following function:
function pointToString(point) {
var x = point.x.toFixed(3);
var y = point.y.toFixed(3);
var z = point.z.toFixed(3);
return `{ x: ${x}, y: ${y}, z: ${z} }`;
}
Now, let's see how the easier-to-use and user-friendlier Transient Tags Editor interface works ...
How the Transient Tag Editor works
The Intersection Inspector is enough if you just have a few __Mattertag__s to set permanently in a few models in your application. But if you need your users to set tags interactively in models, something like the Transient Tags Editor's GUI is a better starting point.
The main advantage of using the Transient Tags Editor is that you can see how the Mattertag will be displayed before creating it and! That allows you to place the tag precisely without trial and error ...
To add a tag, you must click on the "Place New Tag" button to enter the "add tag" mode, then you can place one new tag anywhere you want.
We will only focus on that aspect of the editor and produce a simplified code sample that only add tags:
As the user move a tag along the pointer when in "add tag" mode, the first step is to create a new tag and place it. Let's create a function for that using Mattertag.add():
function addTag() {
if(!tag){
mpSdk.Mattertag.add([{
label: "Matterport Tag",
description: "",
anchorPosition: {x: 0, y: 0, z: 0},
stemVector: {x:0, y: 0, z: 0},
color: {r: 1, g: 0, b: 0},
}])
.then((sid) => {
tag = sid[0];
})
.catch( (e) => {
console.error(e);
})
}
}
Then we will have to place the tag at a position near the pointer, and update its position as the user moves the pointer, so let's create a function for that using Mattertag.editPosition():
function updateTagPos(newPos, newNorm=undefined, scale=undefined){
if(!newPos) return;
if(!scale) scale = .33;
if(!newNorm) newNorm = {x: 0, y: 1, z: 0};
mpSdk.Mattertag.editPosition(tag, {
anchorPosition: newPos,
stemVector: {
x: scale * newNorm.x,
y: scale * newNorm.y,
z: scale * newNorm.z,
}
})
.catch(e =>{
console.error(e);
tag = null;
});
}
As you can see the updateTagPos() function takes 3 parameters:
newPos: the new anchor position for the Mattertag.
newNorm: an optional new stem vector for the Mattertag.
scale: an optional new scale for the stem of the Mattertag.
To update the tag position as the user moves the pointer, let's observe the pointer's intersection property to call updateTagPos():
mpSdk.Pointer.intersection.subscribe(intersectionData => {
if(tag){
if(intersectionData.object === 'intersectedobject.model' || intersectionData.object === 'intersectedobject.sweep'){
updateTagPos(intersectionData.position, intersectionData.normal);
}
}
});
To place the new tag, the user simply clicks their mouse button, the Transient Tags Editor provides its own version of the document.activeElement method for intercepting clicks on the <iframe> (but does not work with Firefox so the editor use a quite complex workaround):
function focusDetect(){
const eventListener = window.addEventListener('blur', function() {
if (document.activeElement === iframe) {
placeTag(); //function you want to call on click
setTimeout(function(){ window.focus(); }, 0);
}
window.removeEventListener('blur', eventListener );
});
}
But, we will use our version which works better with Firefox (But still stop working after the first click in Firefox for whatever reason):
window.addEventListener('blur',function(){
window.setTimeout(function () {
if (document.activeElement === iframe) {
placeTag(); //function you want to call on click
window.focus()
addTag();
}
}, 0);
});
Finally, let's the function that navigates to the new tag and opens its billboard, usingMattertag.navigateToTag()
function placeTag(){
if(tag) mpSdk.Mattertag.navigateToTag(tag, mpSdk.Mattertag.Transition.INSTANT);
tag = null;
}
Simple Editor Code Sample
First, the complete JavaScript source code:
"use strict";
const sdkKey = "aaaaXaaaXaaaXaaaXaaaXaaa"
const modelSid = "iL4RdJqi2yK";
let iframe;
let tag;
document.addEventListener("DOMContentLoaded", () => {
iframe = document.querySelector('.showcase');
iframe.setAttribute('src', `https://my.matterport.com/show/?m=${modelSid}&help=0&play=1&qs=1&gt=0&hr=0`);
iframe.addEventListener('load', () => showcaseLoader(iframe));
});
function showcaseLoader(iframe){
try{
window.MP_SDK.connect(iframe, sdkKey, '3.10')
.then(loadedShowcaseHandler)
.catch(console.error);
} catch(e){
console.error(e);
}
}
function loadedShowcaseHandler(mpSdk){
addTag()
function placeTag(){
if(tag) mpSdk.Mattertag.navigateToTag(tag, mpSdk.Mattertag.Transition.INSTANT);
tag = null;
}
window.addEventListener('blur',function(){
window.setTimeout(function () {
if (document.activeElement === iframe) {
placeTag(); //function you want to call on click
window.focus()
addTag();
}
}, 0);
});
function updateTagPos(newPos, newNorm=undefined, scale=undefined){
if(!newPos) return;
if(!scale) scale = .33;
if(!newNorm) newNorm = {x: 0, y: 1, z: 0};
mpSdk.Mattertag.editPosition(tag, {
anchorPosition: newPos,
stemVector: {
x: scale * newNorm.x,
y: scale * newNorm.y,
z: scale * newNorm.z,
}
})
.catch(e =>{
console.error(e);
tag = null;
});
}
mpSdk.Pointer.intersection.subscribe(intersectionData => {
if(tag){
if(intersectionData.object === 'intersectedobject.model' || intersectionData.object === 'intersectedobject.sweep'){
updateTagPos(intersectionData.position, intersectionData.normal);
}
}
});
function addTag() {
if(!tag){
mpSdk.Mattertag.add([{
label: "Matterport Tag",
description: "",
anchorPosition: {x: 0, y: 0, z: 0},
stemVector: {x:0, y: 0, z: 0},
color: {r: 1, g: 0, b: 0},
}])
.then((sid) => {
tag = sid[0];
})
.catch( (e) => {
console.error(e);
})
}
}
} // loadedShowcaseHandler
The HTML source code:
<!DOCTYPE html>
<html>
<head>
<title>Transient Tag Editor</title>
<style>
#showcase {
width: 100%;
height: 100vh;
}
</style>
<script src="https://static.matterport.com/showcase-sdk/2.0.1-0-g64e7e88/sdk.js"></script>
<script src="/js/app-editor.js" type="text/javascript" defer></script>
</head>
<body>
<iframe id="showcase" frameborder="0" allowFullScreen allow="xr-spatial-tracking"></iframe>
</body>
</html>
It works!
Complete Code
The complete code for this sample and others is available on github:
github.com/loic-meister-guild/pj-matterport-sdk-2021-tutorial
See Also
Matterport SDK 2021 Tutorial
Node.js + Express Tutorial for 2021
How to detect a click event on a cross domain iframe

AddEventListener only works with the last picture

I have one problem. addEventListener only works with the last element of the loop. I know what is the problem, but I can't figure it out. I get the JSON object from another function with the information. Later on the left side there should be clickable pictures. After clicking it I should get the same picture on the right side showed. Still it works only with the last one.
function myFunction(obj) {
var listItems = document.getElementsByClassName("newimg");
for (var i = 0; i < obj.length; i++) {
(function (i) {
document.getElementById("imgSmall").innerHTML += `<br></br><img id="${i}" class="newimg" src=${obj[i].download_url} >`;
let p = obj[i];
listItems[i].addEventListener('click', function() { makeithappen(p);},true);
}(i));
//obj[i].width,obj[i].height,obj[i].author,obj[i].download_url>
}
}
function makeithappen(k) {
document.getElementById("imgLarge").innerHTML = `<br class="text"> AUTHOR: ${k.author}, WIDTH: ${k.width}, HEIGHT: ${k.height}</br><img class="img2" src=${k.download_url} >`;
}
For quick fix.
Replace in your code
listItems[i].addEventListener('click', function() { makeithappen(p);},true);
with
listItems[i].onload = function() {
listItems[i].addEventListener('click', function () { makeithappen(p); }, true);
}
So when you got your listItems you weren't finished with the creation of more images. So new image means new list.
for (let i = 0; i < obj.length; i++) {
document.getElementById("imgSmall").innerHTML += `<br></br><img id="${i}" class="newimg" src=${obj[i].download_url}>`;
const listItems = document.getElementsByClassName("newimg");
listItems[i].addEventListener('click', function () { makeithappen(p); }, true);
}
function makeithappen(k) {
document.getElementById("imgLarge").innerHTML = `<br class="text"> AUTHOR: ${k.author}, WIDTH: ${k.width}, HEIGHT: ${k.height}</br><img class="img2" src=${k.download_url} >`;
}
Pleas do refactor <br></br> into something with css, margin or padding or whatever. This will then allow you to create the images with let div = document.createElement('img') and bind the event listener directly div.addEventlistener(...)

How to toggle-off the react-native-elements tooltip from another component

I want to manually close the tooltip but there are no documents on the react-native-elements site.
So I look over the tooltip code from github and noticed that it has a toggleTooltip function to toggle. Unfortunately I couldn't make it work.
This is the sample code for the tooltip
import { Tooltip } from 'react-native-elements';
render() {
return (
<Tooltip
ref="tooltip"
popover={
<ComponentTest
toggle={this.refs.tooltip}
>
>
<Text>Click me</Text>
</Tooltip>
);
}
The sample code for the ComponentTest
import { Button } from 'react-native-elements';
toggleOff = () => {
this.props.toggleTooltip;
}
render() {
return (
<Button
title="hide"
type="outline"
onPress={this.toggleOff}
/>
);
}
And this is the function from the tooltip.js that I am trying to use. The full code of the tooltip can found here https://github.com/react-native-training/react-native-elements/blob/master/src/tooltip/Tooltip.js
toggleTooltip = () => {
const { onClose } = this.props;
this.getElementPosition();
this.setState(prevState => {
if (prevState.isVisible && !isIOS) {
onClose && onClose();
}
return { isVisible: !prevState.isVisible };
});
};
i am new to react-native and was trying to use tooltip, what i found out that whenever u click inside the component which is popovered , it navigates to whatever onpress function u have written on that particular component and the tooltip doesn't closes,,it also remain mounted when u navigate to other pages,,one solution to it is that use react-native-popup-menu.its the best that we can use for now as a tooltip https://www.npmjs.com/package/react-native-popup-menu
It may be a stupid solution, but did you tried using this.props.toggleTooltip() ?
OH , and ref is not a string anymore, it's a function
<Tooltip
ref={ref => (this.tooltip = ref)}
popover={
<ComponentTest
toggle={this.tooltip}
>
>
On line 191 of Tooltip.js:
<TouchableOpacity onPress={this.toggleTooltip}>
{this.renderContent(true)}
</TouchableOpacity>
and in the definition of renderContent:112 on line 137, it is rendered your popover:
Thus wherever you touch in your popover will make it disappear. I don't know how to disable this behaviour, but I still want to know if and how the visibility of the popover can be controlled from the Tooltip's child element at least.
Just set its style to display:'none' after you touch your popover.
maybe try this way:
state = { theDisplay: 'flex' };
...
componentDidUpdate(prevProps: any) {
if (!prevProps.isFocused && this.props.isFocused) {
this.setState({ theDisplay: 'flex' });
}
}
...
<Popover.Item
value={'response'}
onSelect={() => {
this.setState({ theDisplay: 'none' });
navigate('NoticeResponse', { id: item.id });
}}>
<Text style={styles.toolsItem}>已读信息</Text>
</Popover.Item>
This is my own way of dealing with it. I hope it will help you.
DISCLAIMER I used the ref example in order to get my code to work, but it's something like this:
const tooltipRef = useRef(null);
const foo = (event, index) => {
event.stopPropagation();
tooltipRef.current.toggleTooltip()
}
...
<Tooltip
height={200}
ref={tooltipRef}
popover={<TouchableOpacity onPress={(event) => foo(event, index)}
/>
I had originally tried to implement this by simply using the tooltipRef.current.toggleTooltip() like in the example but it never ended up working because the event was propagating and continuing to toggle it on its own (effectively toggling it twice).
Without any 3rd party library, simple tooltip for both iOS and android can be implemented as follows:
onPress={() =>
Alert.alert("My Title", "My Msg", [], {
cancelable: true
})
}
React native elements documentation show that we can manually turn off the tooltip.
Docs
Store a reference to the Tooltip in your component by using the ref prop provided by React
const tooltipRef = useRef(null);
...
<Tooltip
ref={tooltipRef}
...
/>
Then you can manually trigger tooltip from anywhere for example when screen loads:
useEffect(() => {
tooltipRef.current.toggleTooltip();
}, []);

Scroll multiple drop containers with jQuery UI draggable/droppable?

Using jQuery UI draggable/droppable, how can force multiple drop containers to scroll when the draggable is dragged over them?
For example, how can I make these "drop target" lists scroll when dragging the "drag me" square over them?
Fiddle of above: http://jsfiddle.net/AdrkG/
Note: the draggable({ scroll: true }) option will not work here, as the draggable isn't a child of either drop container.
And some code examples to satisfy StackOverflow (otherwise it complains that I'm only linking to JSFiddle):
<div class="draggable">drag me</div>
<div class="dropcontainer">
<div class="droppable">drop target 0</div>
<div class="droppable">drop target 1</div>
…
</div>
<div class="dropcontainer">
<div class="droppable">drop target 0</div>
<div class="droppable">drop target 1</div>
…
</div>
<script>
$(".draggable").draggable()
$(".doppable").droppable()
</script>
<style>
.dropcontainer {
overflow: auto;
width: 150px;
height: 100px;
}
</style>
You may use the drag event.
Here is an exemple: http://jsfiddle.net/AdrkG/8/
I have almost th the same problem now. Thanks #Bali Balo for the direction and great example.
I just want to show 2-dimentional scroll variant of his code if somebody else needs:
var dropContainers = $(".dropContainer");
drag: function (event, ui) {
dropContainers.each(function () {
var $this = $(this);
var cOffset = $this.offset();
var bottomPos = cOffset.top + $this.height();
var rightPos = cOffset.left + $this.width();
clearInterval($this.data('timerScroll'));
$this.data('timerScroll', false);
if (event.pageX >= cOffset.left && event.pageX <= cOffset.left + $this.width()) {
if (event.pageY >= bottomPos - triggerZone && event.pageY <= bottomPos) {
var moveDown = function () {
$this.scrollTop($this.scrollTop() + scrollSpeed);
};
$this.data('timerScroll', setInterval(moveDown, 10));
moveDown();
}
if (event.pageY >= cOffset.top && event.pageY <= cOffset.top + triggerZone) {
var moveUp = function () {
$this.scrollTop($this.scrollTop() - scrollSpeed);
};
$this.data('timerScroll', setInterval(moveUp, 10));
moveUp();
}
}
if (event.pageY >= cOffset.top && event.pageY <= cOffset.top + $this.height()) {
if (event.pageX >= rightPos - triggerZone && event.pageX <= rightPos) {
var moveRight = function () {
$this.scrollLeft($this.scrollLeft() + scrollSpeed);
};
$this.data('timerScroll', setInterval(moveRight, 10));
moveRight();
}
if (event.pageX >= cOffset.left && event.pageX <= cOffset.left + triggerZone) {
var moveLeft = function () {
$this.scrollLeft($this.scrollLeft() - scrollSpeed);
};
$this.data('timerScroll', setInterval(moveLeft, 10));
moveLeft();
}
}
});
},
I added optimization not to search the droppable areas on every drag event - I pre-calculated it before initializing draggable widget. That substantially increased performance and responsiveness of dragging.
One more minor change is that it looks like moveUp and moveDown function names were interchanged ocasionally (I renamed them vise versa).

Resources