Jump to content
GreenSock

Search In
  • More options...
Find results that contain...
Find results in...
MattE

Dragging a draggable element out of a scrollable div

Go to solution Solved by OSUblake,

Warning: Please note

This thread was started before GSAP 3 was released. Some information, especially the syntax, may be out of date for GSAP 3. Please see the GSAP 3 migration guide and release notes for more information about how to update the code to GSAP 3's syntax. 

Recommended Posts

Hi!

 

I've just discovered GreenSock yesterday and the whole suite of products and it's kinda awesome.

Learning the basics is very simple and even more advanced stuff is pretty accessible.

 

But sadly I'm facing an issue with Draggable.

I've noticed that it it's not possible to drag an element out of a scrollable div.

The element just keeps scrolling in it's own div, but he can't escape it's own div.

 

To clearify the situation:

I'm buidling a scrollable bar at the top of the page (which kan be hidden/shown).

That bar contains draggable elements that will be dragged onto an element that is somewhere at the bottom of the page.

 

Is it possble to disable some kind of CSS in the onDragStart to allow exiting the scrollable div or something else?

 

This codepen showcases the issue: 

 

Thanks!

See the Pen pvYgER by Mattttt (@Mattttt) on CodePen

Link to comment
Share on other sites

Hi MattE

 

You could manually change the DOM structure when you start to drag, like appending your draggable to the letterbar, but that could end up being complicated real quick. It might be easier to create some sort of clone or proxy element and use that as your draggable, which Rodrigo explains in this thread.

 

See the Pen LEyLxp by rhernando (@rhernando) on CodePen

  • Like 5
Link to comment
Share on other sites

Hi MattE  :)

 

Something like this can help you : 

See the Pen xbBOwE by MAW (@MAW) on CodePen

 

Thanks for the quick reply (and sorry for my delayed reply)

 

I've used your provided code and it solves a lot of issues (but it creates some more, yay web development!)

 

Here is a new CodePen I've created: 

See the Pen KwEBdE by Mattttt (@Mattttt) on CodePen

 

Let me list the positive things :-)

- Items can now be dragged out of the scrollable div

- Items are added to the endzone

- Items are removed from the startzone after being dragged into the endzone

 

The more negative things (issues)

- When picking a letter that is not visible at the start (example: Y), the letter isn't shown when dragging because the scrollbar disappears

- When dragging a letter, all the letters are shown above the buttons. This isn't a major issue, but it's noticeable.

- When releasing something in the endzone, the animation starts from a weird position. It would be logical that it start from the position where the mouse has released the element.

 

My guess is that this has to do with the parameters in the TweenLite.to method, but I'm not really familiar with that yet (but I'm working on it).

 

- The option will exist to drag elements from the endzone back to the startzone.

It is best to work with an if/else or just create a new collection of draggables when they've been dropped in the endzone?

 

I'm aware that this is a huge list, but it would be awesome if the basic functionality works!

Link to comment
Share on other sites

Check out my cloning technology  ;-)

 

I did basically what I described above. Created clones that are not attached to the scroll box, and made those my draggables.

 

See the Pen XJQKVX by osublake (@osublake) on CodePen

  • Like 2
Link to comment
Share on other sites

Incredible, Blake!

Link to comment
Share on other sites

Great Job Blake! ... jQuery Draggable uses a helper clone function to handle the same issue with dragging items outside of a scrolling element... good thinking! :)

  • Like 1
Link to comment
Share on other sites

I had no idea jQuery did the same kind of thing, but it seemed like the only logical way to break out of an overflow container.

Link to comment
Share on other sites

Check out my cloning technology  ;-)

 

I did basically what I described above. Created clones that are not attached to the scroll box, and made those my draggables.

 

See the Pen XJQKVX by osublake (@osublake) on CodePen

 

Woah!

 

It took me a lot of time to finally figure out exactly how everything works.

 

I must salute you sir, thinking of this solution for the issue is just incredible.

Tomorrow I'm going to implement this into my already existing code or maybe continue working on your (gonna think on that) to get to the result I want.

 

Jihaa!

Link to comment
Share on other sites

Just let me know if you need help understanding how or why something works. I know it can be confusing when you are first starting out. For anybody else that is interested, here's an overview of how it works.

 

The app starts off by creating clones for each letter with the following scope

 

element - the tile element located in the div#scroll-box
wrapper - the element's parent, which is used to animate the space collapsing when the tile is dragged outside of its box

clone - a clone of the element that gets appended to the div#container

draggable - the draggable instance used by the clone

placed - is true when the tile is appended to the div#end-zone

moved - is true when the tile has been dragged outside of its wrapper

x, y - starting position of the element 

 

When the user clicks on a tile, startDraggable is called, which sets everything else in motion...

 

StartDraggable

  • calculates the position of the element
  • if this is its first run, it will create a new draggable instance
  • hides the element while moving the clone to its position
  • starts the draggable instance by passing in the click event to its startDrag method

 

OnDrag

  • checks if the clone is outside of the wrapper using hitTest
  • if outside, the moved flag is set and it animates the space collapsing

 

OnRelease

  • checks if the clone is inside the end zone using hitTest
  • if its inside and not already placed, it calls addToZone to move the tile to end-zone
  • otherwise it will call moveBack, which will move the tile back to it's original starting place

 

MoveBack

  • animates the wrapper space expanding
  • animates the clone moving back to it's starting position
  • when the animation is complete, it hides the clone and makes the element visibile

 

AddToZone

  • appends the wrapper to the end-zone
  • expands the wrapper's width, which should be collapsed
  • calculates the clones position relative to the wrapper's new position
  • hides the clone and makes the element visible 
  • animates the element moving from the clones position back to the wrapper

 

  • Like 1
Link to comment
Share on other sites

Thanks for the write up, Blake Really interesting and helpful. You make it sound so easy.

I find this summary easier to digest than inline comments broken up all over the code.

 

Since you went through the work of explaining it all, I'd say paste that whole chunk in one comment block beneath your js. 

I think a lot of folks will find it helpful.

 

Excellent work on the demo and explanation. Very cool.

Link to comment
Share on other sites

Well, I'm guessing my notes to understand the code are sort of useless right now :-)

 

adykue.jpg

 

I've created this CodePen to show my current progress : 

See the Pen KwYZOm by Mattttt (@Mattttt) on CodePen

.

You can ignore the endzone for a moment, I don't need it in my project.

 

Things that work:

- Images can be dragged from the startZone on top of the image

- Everything animates like it should (it starts from the correct start to the correct destination)

 

Things to do:

- Make elements from the image droppable again in the startZone (preferably on the same position it originated from).

- Make the elements on the image move randomly (but still in the bounds of the image). To get the feel that they are "floating" on top of the image.

 

Don't mind the tomato, it was the first image I could find :-)

 

If you have some remarks on my code (most likely), don't hold back!

I'm still learning and I've come a long way (in my opinion) but I'm still trying to learn (from kinda the best!)

 

EDIT:

Don't mind the debugging information or weird choice of colors. 

It help me keep track of some elements when coding

 

EDIT 2:

I've also noticed that the dragging semi-works on mobile devices.

The dragging gets initiated, but gets stuck and just stops.

I'm not quite sure yet what's causing the error, but I'm going to try to find out (maybe the onmousedown has something to do with it)

Link to comment
Share on other sites

  • Solution

Chicken scratch! The only words I could read were "Draggable, letter.forEach, and Check Hit".

 

So I took Carl's advice and put the description in the code. While I was in there, I did some refactoring, like adding jQuery to simplify some things so other people might have an easier time understanding it. I like where this is going, so I think I'm going to have to do a part II, implementing some of the features that were just added to Draggable, like autoScrolling.

 

Original version:

See the Pen RNdBOe by osublake (@osublake) on CodePen

 

Refactored version:

See the Pen XJQKVX by osublake (@osublake) on CodePen

 

I totally understand the colors. Before I posted this to CodePen, I had a toggle button on there that would add outlines and background colors so I could see where the elements were.

 

Anyways, you're right about the touch messing up because of the mousedown. Adding a touchstart event seems to have fixed that. I'll have to look at  your code later tonight to see what you got going on with the tiles moving around when you press them. Something looks weird.

 

Moving tiles back to their original position shouldn't be too hard. Just record its position before you remove it. You can check out how I did it in this demo. When you drag a tile out of bounds and release it, it will go back to it's last position. Because the grid is always being sorted in a certain order, I just record it's index and not the actual coordinates.

 

See the Pen RNLdpz by osublake (@osublake) on CodePen

  • Like 2
Link to comment
Share on other sites

Hah, don't mind my writing!

It was just to pen down the logic of your code, so once I understoond it, my notes were kind of useless.

 

I must say your explanation of your code is kind of awesome. Even the refactored version is an huge improvement.

 

I've extended your code to make the elements draggable from the top to the bottom and back without any issues.

I'm fairly confident that I used an acceptable solution :-)

 

--> 

See the Pen ogOmMN by Mattttt (@Mattttt) on CodePen

Link to comment
Share on other sites

You did a good job implementing the sort, except for the last letter. But I discovered that it gets out sync because you are not using the start index you recorded. That's an easy fix though. You can add the letter scopes to an array, and then you can filter it based on where the letter is contained and its index.

 

I also noticed that your auto sizing container was messing up the positioning because the letters recorded the start position before the container resized. Another easy fix. Just add the clones to whatever container the letters are being added to.

 

So I added some functionality to automatically create droppable areas by just adding some attributes to an element. If you add the data-sorted="true", it will place the letters in the correct order, otherwise it will just do a normal append. You can see that the middle container in my demo does not do a sort.

<section drop-zone="bottom" data-sorted="true">
  <div class="clone-container">
    <div class="letter-container"></div>
  </div>
</section>

I somehow managed to add more functionality, while reducing the code.

 

See the Pen dPEbey by osublake (@osublake) on CodePen

 

If you wondering how to make a floating animation, the wander tween in this example might be helpful. You basically create some random position to move to, and then when the animation is over, call the function again to create a new random position. Rinse and repeat.

 

See the Pen kingu by GreenSock (@GreenSock) on CodePen

Link to comment
Share on other sites

Your code keeps changing drastically every time :-)

 

I'm nearly finished with implementing the whole GSAP stuff, but 1 thing still doesn't work.

Currently I'm iterating over the draggable elements so I can execute the "animate" method onto them.

Good part: jQuery finds the elements

Bad part: I can't find the values linked to them.

var lettersToAnimateInSoup = $(".tile");
      lettersToAnimateInSoup.each(function(index, value)
      {
        console.log("this: " + this);
        console.log("1: " + this.inSoep);
        console.log("1.5: " + value.inSoep);
        if (this.inSoep)
        {
          if (this.animateTween) 
          {
            this.animateTween.kill();
            this.animateTween = null;
            TweenLite.to(this.element, 0.5, {x:0, y:0});
          } 
          else 
            console.log("3");
          {
            animateLetter(this);
          }
        }
      })

I can't find any of the objects linked to the the tile. 

Both "value" and 'this' are div's, but they return "undefined" when asking for the "inSoep" value, which is a boolean.

 

I know this doesn't work because the values aren't linked to the HTML DOM, but how can I find the linked elements?

 

In case this should be important:

tiles = $(".tile");
  tiles.each(function (index) {
    amountOfTiles += 1;

    var element = $(this);
    var wrapper = element.parent();
    var offset = element.position();

    var scope = {
      clone: element.clone().addClass("clone").prependTo(container),
      element: element,
      wrapper: wrapper,
      width: wrapper.outerWidth(),
      dropped: false,
      moved: false,
      inSoep: false,
      startIndex: index,
      animateTween: null,
      get x() {
        return getPosition(wrapper, offset).x;
      },
      get y() {
        return getPosition(wrapper, offset).y;
      }
    };

    scope.draggable = createDraggable(scope);

    element.bind("mousedown touchstart", scope, startDraggable);
  });
Link to comment
Share on other sites

Sorry if the code changes confused you. But there was one important thing added to the last revision, and that was adding all the scopes to an array so you can lookup values (~line 116). Now you can create a function like this to return the scope.

// Add your scopes here
var letters = [];

// Get your scope
function getScope(element) {
  var index = letters.map(function(letter) {
    return letter.element[0]; // Unwrap jQuery object
  }).indexOf(element);
  
  return letters[index];
}

You can test it out doing this.

$(".tile").each(function() {
  var scope = getScope(this);
  console.log("SCOPE: ", scope);
});
  • Like 2
Link to comment
Share on other sites

  • 9 months later...

Hello,

 

Today I am bumped into the Draggable goodness of Greensock. First of all, it is really slick and the demos are impressive. I have been reading through this specific thread, because I am trying to do a similar thing in my hobby project. I am also trying to scroll items from a scrollable div to a fixed sized canvas. I have taken one of Blake's last examples (

See the Pen dPEbey by osublake (@osublake) on CodePen

) and I am trying to overwrite it with my own 'items'. Up till there everything seems fine and it works.

 

However, I am trying to make something where the items should stick to the position where they are dropped. Items should be inside the drop-panel with no overlap, which means I should put the threshold to zero. I am not sure how I can set the position of the dropped tile. I have tried with TweenLite.set(tile, {x:this.x,y:this.y} but this doesn't seem to work. Should I set the position with css or is there an easy way to do this?

 

Thank you for the feedback and apologies for opening an old thread..

Link to comment
Share on other sites

Those elements are positioned relative, so you would need to do some absolute positioning. And what do you mean by no overlap? You don't want them to be aligned?

Link to comment
Share on other sites

Sorry for the unclarity. By no overlap I mean an item cannot be on the border of the drop-panel but has to be 100% inside of it. Tiles themselves can overlap each other in the drop-panel.

 

I have made an JQuery UI version of this problem where I can position tiles based on the position of the drop panel and the position of the dropped tile. I calculate the difference between the divs and position the tile relative to its parent (drop-panel) with the difference. I don't need alignment of the tiles.

 

By accident I saw the tile keeping the right position inside the drop-panel because I broke the dropTile function, but that doesn't sound like the right solution :)

Link to comment
Share on other sites

I put a red border around the wrapper to help you see what's going on. All the changes I made are inside the moveBack function. The way I set that demo up is if a className is passed in to that function, then that means the tile should be dropped in another container. Knowing that, I set the wrapper's position to absolute and position it to where the clone is using the getPosition function. I just guessed at the offset to pass in. The flexbox styling and red border is going to make a little off.

 

To make the tiles completely inside a container, I changed the threshold variable at the top of code to 100%, which is used for the hit test.

 

See the Pen e83ab9425f6c835bd1d622b10f330001 by osublake (@osublake) on CodePen

  • Like 3
Link to comment
Share on other sites

Thank you Blake, that is already closer to what I would like to do! The threshold parameter is clear and the offset calculation too. I hope I can ask you two more questions/requirements. 

 

1. Is it possible to re-position the tile once they are dropped in the drop-panel? So if I would like to move the dropped tile 1 from the top left corner of the drop-panel to the bottom right corner of the panel? (And after that still be able to remove it from the drop-panel to the tile-container)
2. In your previous codepen the tile would be positioned properly again (alphabetical order) when you would move it back from the drop-panel to the tile-container. In your last codepen that is broken. Is there a way to do that?

 

Thank you again for your effort. I really appreciate it. 

Link to comment
Share on other sites

That's all possible, but I was wondering if you have a example of what you are trying to do? Or an image?

 

In the meantime, here are some other draggable demos that you may find useful. Most of them align to a grid somehow.

 

Drag and drop

See the Pen pJKRWy by osublake (@osublake) on CodePen

See the Pen waVYvz?editors=0010 by osublake (@osublake) on CodePen

See the Pen RNLdpz by osublake (@osublake) on CodePen

 

Snapping

See the Pen WbVRrj by osublake (@osublake) on CodePen

 

Reordering tiles

See the Pen wMJeRb by osublake (@osublake) on CodePen

  • Like 4
Link to comment
Share on other sites

I have tried to make a JSFiddle, because it was easier to make my example: https://jsfiddle.net/jitsejan/n6hy39qe/3/

 

You can pick an item from the blue box and drop it in the red box below. Within both boxes items can be moved and are not aligned. When an item is dragged from the blue box outside the red box and released, it will revert to the blue box because it is not a drop zone. When an item is dragged from the red box to the blue box, it is removed from the blue box. When you click submit, it will create a form to post the positions for the items that are in the red box when submitted. 

 

The submitting part works fine (probably can be nicer, but I'm just trying :) ). However, I would like the horizontal scrollbar with the drop zone below using GreenSock because I notice that when I run my version without GreenSock, positions get messed up when the screen is resized. I bet it is also possible to do it better with JQuery only, but I would like to learn the GreenSock Draggable. For the blue box I would like the items to be ordered and aligned, like in the Codepen you made before (so when removing and adding them back items stay in the same order), but in the red box the positions can be random.

 

Does this give a better idea of what I am trying to achieve? 

Link to comment
Share on other sites

Based on your example.. you might want to consider using position absolute for your draggable and droppable div tags. Then you can set the position offsets like top and left inside your CSS stylesheet, so when the page is resized the elements can move or adjust their size withe page resize. Right now you are using CSS floats. So when the page is resized your elements wont adjust their position properly. You can also look into using percentages for your containers along with CSS floats. Or using percentages with position absolute and relative.

 

Also you can look into GSAP xPercent and yPercent for responisve layouts:

 

http://greensock.com/gsap-1-13-1

 

And the CSSPlugin page has more info about xPercent and yPercent:

 

http://greensock.com/docs/#/HTML5/GSAP/Plugins/CSSPlugin/

 

Hope this helps :)

  • Like 2
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×