mallanaga Posted June 26, 2020 Author Share Posted June 26, 2020 The example is contrived... trying to imitate a pub/sub system where there are several clients all receiving instructions to move a card to a new position, and potentially append that card to a new parent. I think the loop captured that appropriately. This is the real code. Event Listener receives the event: // subscription.js eventSource.addEventListener("positionCard", (e) => { const message = JSON.parse(e.data); const target = document.getElementById(message.id); const oldParent = document.getElementById(message.oldParentId); const dropArea = document.getElementById(message.dropAreaId); const a = dropArea.getBoundingClientRect(); const b = oldParent.getBoundingClientRect(); cardAnimations.move({ target, dropArea, position: { x: parseFloat(message.x) + (b.left - a.left), y: parseFloat(message.y) + (b.top - a.top), r: parseFloat(message.r) } }); }); // cardAnimations.js const move = ({ target, dropArea, position }) => { dropArea.append(target); let x, y, rotation; if(dropArea.id !== 'table') { x = `random(-10, 10, 1)`; y = `random(-10, 10, 1)`; rotation = `random(-20, 20, 1)_short`; } else { x = position.x || 0; y = position.y || 0; rotation = `${(position.r || 0)}_short`; } gsap.to(target, {x, y, duration: 0.3}); gsap.to(target.querySelector('.Card'), {rotation, duration: 0.3}); Draggable.get(target).update(true, true); }; I'll keep poking at it. Sorry to bother. And thanks for the tip about the border! I'll see if I can pull that up. Link to comment Share on other sites More sharing options...
OSUblake Posted June 26, 2020 Share Posted June 26, 2020 You don't need this line. You're updating the draggable onPress. Draggable.get(target).update(true, true); When reparenting, you should do a FLIP animation. See the Pen GxYPOX by osublake (@osublake) on CodePen See the Pen oNveWXv by GreenSock (@GreenSock) on CodePen 4 Link to comment Share on other sites More sharing options...
mallanaga Posted June 26, 2020 Author Share Posted June 26, 2020 Thanks for the knowledge, Blake! That's super helpful. I'm not an animator, lol, but I'm slowly becoming one. Regarding the update. The clients won't have the benefit of an onPress event. Won't they need a "manual" call to update? or would the tween be sufficient? Link to comment Share on other sites More sharing options...
OSUblake Posted June 26, 2020 Share Posted June 26, 2020 59 minutes ago, mallanaga said: Regarding the update. The clients won't have the benefit of an onPress event. Won't they need a "manual" call to update? or would the tween be sufficient? No. A client only needs to call update when they start dragging. It doesn't need to happen on every client. I had a real-time dragging demo up on CodePen, but it looks like my Firebase account is all screwed up. See the Pen gPeeJN by osublake (@osublake) on CodePen But you can see how I handle the dragging and animation in the MagnetController class. Every draggable was an instance of the MagnetController class. The onChange event would fire when something changed, like dragging the magnet. class MagnetController { active = false; time = 0; board = document.querySelector(".board"); data = this.firebaseData.magnet(this.magnetId); unwatch = this.data.$watch(this.onChange.bind(this)); draggable = null; constructor( private $element, private $scope, private $timeout, private firebaseData) { this.data.$loaded(event => { if (!this.data.content || !this.data.zIndex) { TweenLite.set($element, { autoAlpha: 0 }); this.remove(); return; } this.init(); }); this.data.$ref().onDisconnect().update({ locked: false }); } init() { this.draggable = new Draggable(this.$element, { onDrag: this.onDrag, onPress: this.onPress, onRelease: this.onRelease, zIndexBoost: false, callbackScope: this, liveSnap: { x: n => this.active ? n : this.data.x, y: n => this.active ? n : this.data.y } }); } onChange(event) { var config = { rotation : this.data.rotation, zIndex : this.data.zIndex || Draggable.zIndex }; if (!this.active) { config.x = this.data.x; config.y = this.data.y; } TweenLite.to(this.$element, this.time, config); this.time = 0.07; } onPress(event) { if (this.data.locked) return; this.draggable.update(); this.updateDraggable(); this.active = true; this.data.locked = true; this.data.zIndex = Draggable.zIndex++; this.data.$save(); } onRelease(event) { if (!this.draggable.hitTest(this.board)) { this.remove(); return; } if (!this.active) return; this.active = false; this.data.locked = false; this.data.$save(); } onDrag(event) { if (!this.active) return; this.data.x = this.draggable.x; this.data.y = this.draggable.y; this.data.$save(); } remove() { this.draggable && this.draggable.kill(); this.unwatch(); this.data.$remove().then(ref => { this.$scope.$destroy(); this.updateBoard(); }, error => { console.log(error); }); } } 2 Link to comment Share on other sites More sharing options...
mallanaga Posted June 27, 2020 Author Share Posted June 27, 2020 const move = ({ target, dropArea, position }) => { // First - record start position let first = { x: 0, y: 0 }; let rect = target.getBoundingClientRect(); first.x = rect.left; first.y = rect.top; // State change, render new DOM dropArea.append(target); // Last - record new position let last = { x: 0, y: 0 }; rect = target.getBoundingClientRect(); last.x = rect.left; last.y = rect.top; // Invert - animate from first position let x, y, rotation; if(dropArea.id !== 'table') { x = `random(-10, 10, 1)`; y = `random(-10, 10, 1)`; rotation = `random(-20, 20, 1)_short`; } else { x = position.x + first.x - last.x; y = position.y + first.y - last.y ; rotation = `${position.r}_short`; } gsap.to(target, {x, y, duration: 0.3}); gsap.to(target.querySelector('.Card'), {rotation, duration: 0.3}); }; So that's pretty clean, and I was able to move more of the code into that last function. This is great. No need to send oldParent now. Nice. So, this still jumps, unfortunately - BUT - I feel like I'm honing in on the culprit. I move the "Draw" area to the middle of the table on page load. The "jump" seems to be relative to the position of the Draw area on the table. When I don't move the Draw area, and keep it at 0,0, the animation looks perfect. As soon as I move it, it jumps again. Is there a proper way to move that element? I'm using an inline style, that's calculated on the fly. I'm not positive, but this feels like it might be part of the issue. I tried calling .update() on the Draw area when it was in the middle, and it actually moved to 0,0. Link to comment Share on other sites More sharing options...
OSUblake Posted June 27, 2020 Share Posted June 27, 2020 4 hours ago, mallanaga said: I feel like I'm honing in on the culprit. I move the "Draw" area to the middle of the table on page load. Does that need to be draggable? If every client can move that around, then I think you're going to have to add in some more logic as it's going offset stuff. If it's stationary, then it will be much easier. Link to comment Share on other sites More sharing options...
OSUblake Posted June 27, 2020 Share Posted June 27, 2020 4 hours ago, mallanaga said: As soon as I move it, it jumps again. When you move the area and then drag the card to the table, sending just the position of the card would be incorrect. The position on the table would be the area x/y + the card x/y. If needed, you can grab the x and y values of any element using gsap.getProperty(). https://greensock.com/docs/v3/GSAP/gsap.getProperty() If you're moving the card from the table to another part of the table, then sending just card's x/y would be ok. There would be no need to do any sort of calculation. Just tween to the new position. 1 Link to comment Share on other sites More sharing options...
OSUblake Posted June 27, 2020 Share Posted June 27, 2020 See this demo. See the Pen d922bc6094b169036c7b128118cbff0b by osublake (@osublake) on CodePen 2 1 Link to comment Share on other sites More sharing options...
mallanaga Posted June 27, 2020 Author Share Posted June 27, 2020 Blake! Your command of javascript is incredible. I can't thank you enough. That works a treat, and I was able to massage it into place within my app. Great example using window.dispatchEvent to more closely match my use case! Can you confirm the expected behavior or gsap.getProperty, though? It behaves as expected when the Area starts at 0,0, and I drag it around. If I position the element manually, though, it goes back to jumping. It looks like it's grabbing the offset that the element has moved, and not necessarily the element's absolute position within another element. Seems like I'll want to use getBoundingClientRect here, unless there's another gsap convenience method available? Again... you're a life saver. This one problem stalled the development on my project for weeks. I owe you! 1 Link to comment Share on other sites More sharing options...
OSUblake Posted June 27, 2020 Share Posted June 27, 2020 2 hours ago, mallanaga said: If I position the element manually, though, it goes back to jumping How are you positioning it? gsap.getProperty() returns the transform: translate values, which would be different if you were setting the position with left/top. The transform can be set in your CSS, inline, or with gsap. If you want to use to left/top, then this would need to change. const dx = cardX + (parentX - gsap.getProperty(newParent, "x")); const dy = cardY + (parentY - gsap.getProperty(newParent, "y")); To something like this. const a = newParent.getBoundingClientRect(); const b = oldParent.getBoundingClientRect(); const dx = cardX + (b.left - a.left); const dy = cardY + (b.top - a.top); 1 Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now