Jump to content
Search Community

Clone draggable stickers and reapply location within SVG

Gingah test
Moderator Tag

Recommended Posts

Hello,

I've been reading and trying out different CodePens and code from this forum, but I am stuck trying to reapply the locations from a Draggable accurately within a scaled SVG. This is what I'm doing:

  1. Create draggable stickers from a list of image URLs, as image-elements within the SVG
  2. Clone the image-element and make it draggable
  3. Assign an id-attribute and try to make it unique
  4. Store the source and x,y-location of the image to LocalStorage (using LocalForage)
  5. Rebuild the image-element from LocalStorage and make it draggable onLoad

The SVG is scaled with CSS:

transform: scale(0.65);

You'll notice that dragging a sticker to the lower right corner, for example, won't keep it there when the page is reloaded. It'll reset to somewhere below the original location of the sticker. I think this has to do with this attribute:

transform: matrix(1, 0, 0, 1, x, y);

But I can't figure out what x- and y-property I need to save and apply to preserve the location within the scaled SVG. Has anyone attempted anything similar? My application is a simple map where I drag and drop stickers, and want to save their location for further use.

See the Pen yLYOMvG by Gingah (@Gingah) on CodePen

Link to comment
Share on other sites

Hey Gingah and welcome to the GreenSock forums! I have a few questions about your approach:

 

25 minutes ago, Gingah said:

Create draggable stickers from a list of image URLs, as image-elements within the SVG

Why use an SVG? You're loading non-vector images so why not just use <img> tags?

 

26 minutes ago, Gingah said:

try to make it unique

You method of using a counter would work the first time but on next loads you'd have duplicated IDs. It'd be better to use the current time or generate an actual ID that's more likely to be unique.

 

28 minutes ago, Gingah said:

The SVG is scaled with CSS:


transform: scale(0.65);

Why scale it with a transform? Just set its width/height to what you need it to be.

 

32 minutes ago, Gingah said:

Store the source and x,y-location of the image to LocalStorage (using LocalForage)

Not sure why you would need a library to load them to localstorage. It's pretty simple to do using the native API :) 

 

29 minutes ago, Gingah said:

I can't figure out what x- and y-property I need to save and apply to preserve the location within the scaled SVG

Make sure that the elements are all in the same exact position on the page before transforming them and only use transforms to move them. That way you're guaranteed to have the same position as the last time the page was open.

 

If I were doing it, I'd make them all <img>s, position them absolutely at 0,0, move them to their initial states using transforms, and then save their transforms to localstorage in the onDragEnd.

  • Like 1
Link to comment
Share on other sites

 

11 minutes ago, ZachSaucier said:

Hey Gingah and welcome to the GreenSock forums!

Thanks!

12 minutes ago, ZachSaucier said:

Why use an SVG? You're loading non-vector images so why not just use <img> tags?

The stickers are non-vector, but in the application I also include a lot of vector-elements like rect and text, hence the fairly large frame of the SVG. I just omitted them for brevity.

12 minutes ago, ZachSaucier said:

You method of using a counter would work the first time but on next loads you'd have duplicated IDs. It'd be better to use the current time or generate an actual ID that's more likely to be unique.

I can rectify the IDs as you suggest, I just haven't gotten past the current issue yet.

13 minutes ago, ZachSaucier said:

Why scale it with a transform? Just set its width/height to what you need it to be.

Scaling with transform seemed to be the most apt way to preserve the viewBox- and preserveAspectRatio-attributes of the SVG, at least when I was building the original SVG. This was also easier to apply when the SVG is absolutely positioned, because it will stack with other SVGs and a background image, thus keeping them the same size consistently. The application emulates layers in graphical editors a bit in that regard, overlaying colors in the rectangles.

13 minutes ago, ZachSaucier said:

Not sure why you would need a library to load them to localstorage. It's pretty simple to do using the native API :) 

It probably is easy enough to do with the native API, but the LocalForage API is so elegantly written I just fell for it. Also easy to grab all of the relevant data and POST it somewhere.

14 minutes ago, ZachSaucier said:

Make sure that the elements are all in the same exact position on the page before transforming them and only use transforms to move them. That way you're guaranteed to have the same position as the last time the page was open.

 

If I were doing it, I'd make them all <img>s, position them absolutely at 0,0, move them to their initial states using transforms, and then save their transforms to localstorage in the onDragEnd.

I tried with having the stickers absolutely positioned in a separate "layer" initially, but had some problems maintaining the bounds within the SVG when dragging them. The problem seems to be that the matrix is applied onDragEnd and so the x,y I get from "this.x" or "this.target.getBoundingClientRect().x" is inaccurate when loaded again. This is why I use "sticker.setAttributeNS(null, "transform", `matrix(1, 0, 0, 1, ${x}, ${y})`);" in the build()-function. I also tried setting the x- and y-attribute, but at no point could I get an accurate value for them when reapplying the position.

 

It also produces a rather easy to handle SVG-element that I can easily reuse, change the Z-index of, or transport elsewhere.

 

Does the inaccurate location happen because of the scaling, and would I need to calculate the x and y relative to the SVG itself? I'm a bit confused about what units I am working with and what they are relative to, and how to maintain location when I scale the element(s).

Link to comment
Share on other sites

Also, you can reduce all this code

 

const sticker = document.createElementNS(
  "http://www.w3.org/2000/svg",
  "image"
);
sticker.setAttributeNS(null, "height", "64");
sticker.setAttributeNS(null, "width", "64");
sticker.setAttributeNS("http://www.w3.org/1999/xlink", "href", path);
sticker.setAttributeNS(null, "x", 64 * i + 16 + 8 * i);
sticker.setAttributeNS(null, "y", "0");
sticker.setAttributeNS(null, "visibility", "visible");
sticker.classList.add("sticker");
sticker.dataset.type = `sticker-${i}`;
parent.appendChild(sticker);

 

using insertAdjacentHTML.

https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML

 

parent.insertAdjacentHTML("beforeend", `
  <image class="sticker" x="${64 * i + 16 + 8 * i}" y="0" height="64" width="64" href="${path}" data-type="sticker-${i}"></image>
`);


// if you need the element 
const element = parent.lastElementChild;

 

 

  • Like 4
Link to comment
Share on other sites

15 hours ago, Gingah said:

Does the inaccurate location happen because of the scaling, and would I need to calculate the x and y relative to the SVG itself?

No, the scaling isn't what's throwing it off, I just didn't understand why you needed it. 

 

15 hours ago, Gingah said:

I'm a bit confused about what units I am working with and what they are relative to, and how to maintain location when I scale the element(s).

Most likely the different in positioning is due to setting some values with GSAP and some not with GSAP. We recommend setting the positioning completely with GSAP so GSAP is always aware of all of the information. Blake's approach above is a good way to do it :) 

Link to comment
Share on other sites

5 hours ago, ZachSaucier said:

Most likely the different in positioning is due to setting some values with GSAP and some not with GSAP.

 

I'm not sure what the problem is,  but I did this as a sanity check before making that demo.

 

onDragEnd: function () {
  window.map.setItem(this.target.getAttributeNS(null, "id"), {
    src: this.target.getAttributeNS("http://www.w3.org/1999/xlink", "href"),
    // x: this.target.getBoundingClientRect().x,
    // y: this.target.getBoundingClientRect().y        
    x: this.x,
    y: this.y
  });

  // *** THESE SHOULD MATCH ***
  map.getItem(this.target.id).then(res => {
    console.log("\n")
    console.log(res.x, res.y)
    console.log(this.x, this.y)
  });      
}

 

The values stored are not the same as the draggable, so there has to be a logic error somewhere, or a problem with localForage.

 

Link to comment
Share on other sites

👆 I probably wrote that wrong after looking at the localForage docs.

 

This gives matching results.

 

async onDragEnd() {
      
  await map.setItem(this.target.id, {
    src: this.target.getAttribute("href"),
    x: this.x,
    y: this.y
  });

  const res = await map.getItem(this.target.id);
  console.log("\n");
  console.log(res.x, res.y);
  console.log(this.x, this.y);      
},

 

But some positioning looks off when you reload, and there are some errors in the console.

 

Link to comment
Share on other sites

18 hours ago, OSUblake said:

Those values are not the same thing.

I noticed, and neither seemed to give an accurate reading on where I dropped the stickers.

 

20 hours ago, OSUblake said:

I would do something like this.

At first I thought that was the same example I was working off of earlier, but that's a great addition! For some reason the fork-button disappeared, but I put some of the code I was using into it, and the location-fidelity seems to work exactly as expected: 

See the Pen QWjNeYV by Gingah (@Gingah) on CodePen

 

1 hour ago, OSUblake said:

But some positioning looks off when you reload, and there are some errors in the console.

I'll have to revisit the use of localForage, it might be over the top when I just need to pass an object into storage via POST.

 

@ZachSaucier and @OSUblake, many thanks for your rapid responses and especially that excellent example Blake! This will get me across the finish line.

Link to comment
Share on other sites

3 minutes ago, Gingah said:

At first I thought that was the same example I was working off of earlier, but that's a great addition!

 

Thanks. It's similar to what I did to create an online/real-time word game with draggable and firebase.   

 

8 minutes ago, Gingah said:

For some reason the fork-button disappeared, but I put some of the code I was using into it, and the location-fidelity seems to work exactly as expected: 

 

Yeah, I have CodePen to automatically create private pens, and it won't let you fork private pens. I don't understand the logic behind that because it's just an inconvenience as you can still copy and paste the code.

 

 

  • Like 1
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.
×
×
  • Create New...