[ts-overlay-widget] scale and offset to a percentage of div dimensions
Meanwhile I've fixed x-axis
and y-axis
not working in clip mode!
Thanks for reporting it
Thanks for your responses! The use case is that we are creating stickers using the spine characters, and for some stickers, we want to zoom in quite a bit on the head of the character, and for other stickers, we want to zoom out on the whole body. The stickers should always look consistent when used by the user, so I want to scale it based on the div height.
For example, the spine rig is of the whole body but for the crying sticker, we want it to display like this:
edit: nevermind the image showed up
image upload doesn't seem to be working, here's a link to it
It's still not entirely clear to me why you can't use the CSS padding approach. Perhaps you could help us better understand your problem by providing a sketch with a couple of rectangles showing the two different widgets (zoomed in and not zoomed in) and the issue with sizing.
In any case, as I mentioned above, you can already achieve this by defining your personalized fit mode.
First, let's understand the easiest way to focus on a specific part of your skeleton in the div: by using the bounds object.
For example, if you want to zoom in on Spineboy's head, you can do it like this:
<spine-widget
identifier="boi"
atlas="assets/spineboy-pma.atlas"
skeleton="assets/spineboy-pro.skel"
animation="walk"
debug
clip
></spine-widget>
<script>
(async () => {
const boi = spine.getSpineWidget("boi");
await boi.loadingPromise;
const myBound = {
x: -110,
y: 350,
width: 310,
height: 350,
};
boi.setBounds(myBound);
})();
</script>
However, this won't add any padding since the skeleton is automatically scaled to fit using the default method, which is contain
.
There's no direct way to define your personalized fit mode, but you can set the fit mode to none
and utilize the beforeUpdateWorldTransforms
callback. The none
fit mode doesn't automatically scale the skeleton.beforeUpdateWorldTransforms
callback. The fit mode none
does not autoscale the skeleton.
In the beforeUpdateWorldTransforms
callback, we want to change the scale of the skeleton based on the div size.
We need to:
- get the div size
- determine the target width/height (1/2 of the div width/height) and transform it to the skeleton space
- determine the smallest scale ratio
- scale the skeleton accordingly
<spine-widget
identifier="boi"
atlas="assets/spineboy-pma.atlas"
skeleton="assets/spineboy-pro.skel"
animation="walk"
fit="none"
debug
clip
></spine-widget>
<script>
(async () => {
const boi = spine.getSpineWidget("boi");
const { skeleton } = await boi.loadingPromise;
const myBound = {
x: -110,
y: 350,
width: 310,
height: 350,
};
boi.setBounds(myBound);
boi.beforeUpdateWorldTransforms = (skeleton, state) => {
// get the div size
const containerBounds = boi.getHTMLElementReference().getBoundingClientRect();
// determine the target width/height (1/2 of the div width/height) and transform it to the skeleton space
const targetWidth = containerBounds.width / 2 * window.devicePixelRatio;
const targetHeight = containerBounds.height / 2 * window.devicePixelRatio;
// determine the smallest scale ratio
const bounds = boi.bounds;
const scaleW = targetWidth / bounds.width;
const scaleH = targetHeight / bounds.height;
const scale = Math.min(scaleW, scaleH);
// scale the skeleton accordingly
skeleton.scaleX = scale;
skeleton.scaleY = scale;
}
})();
</script>
Clearly, this isn't the most straightforward approach, but it works.
We might consider adding a padding option or an easier way to create a custom fit mode in the future.
Davide
I'm trying to do something like the following, where there is a grid of zoomed in sticker emotes. I don't have good zoomed out examples right now though! The beforeUpdateWorldTransforms
seems to work, thanks so much for the guidance there. I'll try to use it in combination with y-axis and x-axis next.
I don't see any internal padding here. In this case, a custom bounds + fit="contain" should do the job.
Note that the approach I suggested works well, but is far from ideal. We have padding options in the Spine player, so we'll probably add them here as well.
I'll let you know if and when they become available.
- Изменено
We added the possibility to add a virtual padding to the element container of the widget.
Basically, you can get rid of all the code I suggested above and set:
<spine-widget
identifier="boi"
atlas="assets/spineboy-pma.atlas"
skeleton="assets/spineboy-pro.skel"
animation="walk"
debug
clip
pad-left=".25"
pad-right=".25"
pad-top=".25"
pad-bottom=".25"
></spine-widget>
<script>
(async () => {
const boi = spine.getSpineWidget("boi");
await boi.loadingPromise;
boi.setBounds({
x: -110,
y: 350,
width: 310,
height: 350,
});
})();
</script>
I just want to add one thing. I've noticed that you use the clip
attribute a lot.
Be aware that it decreases the performance on the page. Without the clip
attribute, all skeletons using the same texture are drawn by using a single draw call. Instead, if you use the clip
attribute, each skeleton is drawn in a dedicated draw call.
Davide
Hey Davide,
Thanks for making that change! The reason I am using clip is because we're trying to render stickers like the ones below, where we zoom into the character. As you can see, the bottom is clipped. Sometimes the sides and top are too.
Ah thanks for the note about clipping performance. Let's say on a page, I show 30 stickers that are clipped and 30 that are not clipped, do you think there might be visible performance issues? We do need to clip somehow, so I'm just wondering what the impact is on the user and their device. Worst case, I can render the sticker as a png/webm instead via server or client side rendering but that is not as easy as using the spine runtime directly.
On the topic of performance, I'm also wondering if you think there will be any performance issues with using spine-widget on a web page where we're also streaming a video.
Thanks!
jojo
Performance depends on several factors specific to each use case. You might optimize your webpage as much as possible, but still have bad performance because of several other factors (have a read of this page).
In the example page of webcomponents, there are a lot of skeletons and on my devices it runs always at full FPS. Let's take a section as an example:
There are 15 skeletons on the screen. I have two screens, the one of my MacBook Pro M3 (120Hz), and an external one (60Hz). Then there is also my iPhone 12 mini (60Hz).
If I do not use the clip property, on all displays I get full FPS.
If I set the clip property for all of them, I get 60 FPS for all 60Hz displays, but 110 FPS for the MacBook Pro display. No one will ever notice this 10 FPS drop on 120Hz displays though.
In the extreme case where I'd need to optimize it, would I focus only on the clip property? No, because if you make a further analysis, we discover that there are 60 draw calls even with clip true! The Chibi example stickers as is are not very well optimized for this specific use case. The example has a different texture for each skin. Here we use 4 different textures (3 for the characters and 1 for the common elements). If the goal of this skeleton had been to render these three characters all together, we could have put all of them in a single texture, reducing drastically the number of draw calls.
We could go further and optimize it more. For example, Luke's chibi has a shadow on the face that uses the multiply blend mode. That breaks batching too.
I actually don't think that you will have big problems in displaying your skeletons.
Take also into consideration that the widgets run only if they are visible on the screen. So, if some of them are off screen, these don't affect performance.
Regarding the video + spine widget, there too it might depend on several factors. Maybe you can pause the video when it is off screen. But if eventually everything runs smoothly, you can avoid that.
My advice is to test this as early as possible. If there are performance issues, you can immediately try to identify the problems.
We exposed some parameters to set the bounds from webcomponent's attributes.
The previouse code snipper can now be simplified as is:
<spine-widget
atlas="assets/spineboy-pma.atlas"
skeleton="assets/spineboy-pro.skel"
animation="walk"
debug
clip
pad-left=".25"
pad-right=".25"
pad-top=".25"
pad-bottom=".25"
bounds-x="-100"
bounds-y="350"
bounds-width="310"
bounds-height="350"
></spine-widget>