• Runtimes
  • I would like to fix the problem when the armature is moved by touch.

Related Discussions
...

Nate
Thank you very much for your reply. I understand your answer.
Thank you for the easy-to-understand code.

I guess this will be my last question.

target.localToWorld(dragOffset.set(target.worldX, target.worldY));

The standard position before dragging the skeleton is currently set to target.world.X, target.world.Y, but the standard position does not match the screen coordinates, so it is out of the screen.

To match the coordinates on the screen, shouldn't we use the renderer.camera.screenToWorld code to match the coordinates?

It seems that only the corresponding coordinates need to be solved in the code currently used, but I would like to ask you again how to do this part.

Thanks as always for your answers.

This code doesn't make sense:

target.localToWorld(dragOffset.set(target.worldX, target.worldY));

You don't want to use a world position as a local position. You can use:

target.localToWorld(dragOffset.set(0, 0));

The local position 0,0 for the target bone is the bone's origin and this converts it to world coordinates. This gives the same result:

target.parent.localToWorld(dragOffset.set(target.x, target.y));

There we are using the target bone's local position in its parent bone coordinates. The root bone doesn't have a parent bone though, so needing to check if parent is null is annoying.

And finally this also gives the same result:

dragOffset.set(target.worldX, target.worldY);

When the bone's world transform is computed, using by calling Skeleton updateWorldTransform, the world position is stored, so you can just use that.

Using one of the 3 correct options above may solve your problem of the bone being off screen. Usually it's easiest to keep things in world coordinates and only use screenToWorld to map the mouse position into the world coordinates.

  • elffire님이 이에 답장했습니다.

    Nate
    I used the code you provided.

    target.localToWorld(dragOffset.set(0, 0));

    However, in this code, a problem with broken coordinates appears, so we continue to ask.
    I'll upload it as a video, so please check it out.

    It's hard to say what's wrong. Probably it has to do with half your code using screen coordinates, as you mentioned. Convert the touch from screen to world coordinates and do everything in world coordinates. For example, you do this for every control bone:

    renderer.camera.screenToWorld(coords.set(x, y, 0), canvas_1.clientWidth, canvas_1.clientHeight);

    You only need to do that once and then use world coordinates for everything else.

    Otherwise you will need to debug your code. Check the value of dragOffset after you subtract x and y. It should be the distance from the bone to the touch when the drag started.

    • elffire님이 이에 답장했습니다.

      Nate
      I'll send you an example using the two codes you provided.

      target.parent.localToWorld(dragOffset.set(target.x, target.y));

      When using this code, the values of dragOffset.x and dragOffset.y are all expressed as 0.

      We will upload a video when working with the result value.

      I'm still seeing the same symptoms.

      So I proceeded to work with the code you originally sent.

                 target.localToWorld(dragOffset.set(0, 0));

      When using this code, as I said, the coordinates did not match, so I adjusted the coordinates.

                 renderer.camera.screenToWorld(coords.set(x, y, 0), canvas_1.clientWidth, canvas_1.clientHeight);
      
                 target.localToWorld(dragOffset.set(0, 0));
                 dragOffset.x -= coords.x;
                 dragOffset.y -= coords.y;

      If modified as described above, the values of dragOffset.x and dragOffset.y are modified.

      When I do this, the values of dragOffset.x and dragOffset.y appear, but the problem is still visible.

      We will upload a video of the modified code.

      Same issue. Is there anything else that needs to be changed?

      I'm afraid I can't debug your application code like this. You will need to understand your code and what is happening so you can determine how to get the behavior you desire. I suggest to build a simpler example. Add lots of drawing that shows the position of everything involved: touch down, drag start, the bone position when the drag started, etc. It helps to visualize those positions so it will be obvious when one is wrong.

      • elffire님이 이에 답장했습니다.

        Nate
        I found the problem in the code.

               var newX = startX + 0.20 * (dragX - startX);
               var newY = startY + 0.20 * (dragY - startY);

        The code that makes it move by 0.2 was the problem, and the problem can be solved by applying the code below.

               var newX = dragX;
               var newY = dragY;

        But the code I want is to move only .0.2, is there any other way?

        You probably want (assuming dragOffset is calculated correctly):

        var newX = dragX + dragOffset.x;
        var newY = dragY + dragOffset.y;

        The code to move 20% of the distance between startX and dragX is OK. Probably startX is wrong. You could try moving the bone to startX or rendering lines for the positions, as mentioned, so you can understand what is going on. Again, you probably have a mismatch between world and screen coordinates. Convert any screen coordinates to world and use world for everything.

        • elffire님이 이에 답장했습니다.
        • elffire 님이 이 게시물을 좋아합니다..

          Nate
          After confirming that the coordinates of startX and startY are wrong, they have been changed.

                     renderer.camera.screenToWorld(coords.set(after_startX, after_startY, 0), canvas_1.clientWidth, canvas_1.clientHeight);
                     target.localToWorld(dragOffset.set(0, 0));
                     dragOffset.x -= coords.x;
                     dragOffset.y -= coords.y;

          This code doesn't seem to work.

                 var newX = after_startX + 0.20 * (dragX - after_startX) + dragOffset.x;
                 var newY = after_startY + 0.20 * (dragY - after_startY) + dragOffset.y;

          Replace with video and upload.

          Would it be possible to check it faster if I reduced the entire code and uploaded it in an example format so that I can easily see the code?

          Why do your videos use very fast drags? Touch down, drag so we can see what is happening, touch up. 🙂

          I can see your code in the forum and on your page, but I can't edit it. If you had a simple, self-contained example then I could easily edit the code.

          That code is correct, it calculates a point 20% between after_startX and dragX. The problem must be after_startX, dragX, or dragOffset.x are wrong. Draw them so you can see what is going on! Otherwise you are working in the dark.

          • elffire님이 이에 답장했습니다.
          • elffire 님이 이 게시물을 좋아합니다..

            Nate
            The reason for using a short touch is that users can touch it briefly when changing their hairstyle, so we want to compensate for that problem as well.

            Here's a code you can easily change.

            <html>
            <script src="https://bxkrl.com/spine-runtime/spine-webgl/dist/iife/spine-webgl.js"></script>
            <style>
            * {
            	margin: 0;
            	padding: 0;
            }
            </style>
            
            <body>
            <canvas id="canvas_1" style="position: absolute; width: 100%; height: 100%;"></canvas>
            <script>
            
            var canvas, context, gl, renderer, input, assetManager, input;
            
            var position = new spine.Vector3();
            var coords = new spine.Vector3();
            var mouse = new spine.Vector3();
            var target = null;
            var hoverTargets = [null];
            var haircolorr = 1;
            var haircolorg = 1;
            var haircolorb = 1;
            var haircolora = 1;
            var json_name, atlas_name, hair_scale;
            var skeleton;
            var skeletonJson;
            var offset = new spine.Vector2();
            var bounds = new spine.Vector2();
            var oldX, oldY, dragX, dragY;
            var dragging = false;
            var dragOffset = new spine.Vector2();
            var after_startX, after_startY;
            var startX, startY, currentX, currentY, startTime, endTime, duration;
            
            var controlBones_woman_hair_2 = [
            	"woman_hair_2_bone_1", "woman_hair_2_bone_2", "woman_hair_2_bone_3", "woman_hair_2_bone_4", "woman_hair_2_bone_5", "woman_hair_2_bone_6", "woman_hair_2_bone_7", "woman_hair_2_bone_8", "woman_hair_2_bone_9"
            ];
            controlbones = controlBones_woman_hair_2;
            
            	class App {
            		constructor() {
            			this.skeleton = null;
            			this.animationState = null;
            		}
            
            		loadAssets(canvas) {
            			canvas.assetManager.loadText("assets/woman_hair_2.json");
                canvas.assetManager.loadTextureAtlas("assets/woman_hair_2.atlas");
            		}
            
            		initialize(canvas) {
            			let assetManager = canvas.assetManager;
                let renderer = canvas.renderer;
                var atlas = assetManager.require("assets/woman_hair_2.atlas");
                var atlasLoader = new spine.AtlasAttachmentLoader(atlas);
                skeletonJson = new spine.SkeletonJson(atlasLoader);
            
                var skeletonData = skeletonJson.readSkeletonData(assetManager.require("assets/woman_hair_2.json"));
                this.skeleton = new spine.Skeleton(skeletonData);
                new spine.CameraController(canvas.htmlCanvas, canvas.renderer.camera);
                skeleton = this.skeleton;
                skeleton.setToSetupPose();
            
                skeleton.updateWorldTransform();
                skeleton.getBounds(offset, bounds, []);
                renderer.camera.position.x = offset.x + bounds.x / 2;
                renderer.camera.position.y = offset.y + bounds.y / 2;
            
                renderer.skeletonDebugRenderer.drawPaths = true;
                renderer.skeletonDebugRenderer.drawBones = true;
                renderer.skeletonDebugRenderer.drawMeshHull = true;
                renderer.skeletonDebugRenderer.drawMeshTriangles = true;
            			var input = new spine.Input(canvas_1);
            
            			input.addListener({
            				down: (x, y) => {
            					startX = x;
            					startY = y;
            					var bestDistance = 10000,
            						index = 0;
            					var best;
            					for (var i = 0; i < controlbones.length; i++) {
            						hoverTargets[i] = null;
            						let bone = skeleton.findBone(controlbones[i]);
            						var position = new spine.Vector2(bone.length, 0);
            						bone.localToWorld(position);
            						renderer.camera.screenToWorld(coords.set(x, y, 0), canvas_1.clientWidth, canvas_1.clientHeight);
            						let distance = Math.sqrt((coords.x - bone.x) * (coords.x - bone.x) + (coords.y - bone.y) * (coords.y - bone.y));
            						if (distance < bestDistance) {
            							bestDistance = distance;
            							best = bone;
            							index = i;
            						}
            					}
            					if (best) hoverTargets[index] = best;
            					target = best;
            				},
            				up: (x, y) => {
            					target = null;
            					dragging = false;
            				},
            				dragged: (x, y) => {
            					currentX = x;
            					currentY = y;
            					if (!dragging && (Math.abs(currentX - startX) > 5 || Math.abs(currentY - startY) > 5)) {
            						dragging = true;
            						after_startX = x;
            						after_startY = y;
            						renderer.camera.screenToWorld(coords.set(after_startX, after_startY, 0), canvas_1.clientWidth, canvas_1.clientHeight);
            
            						target.localToWorld(dragOffset.set(0, 0));
            						dragOffset.x -= coords.x;
            						dragOffset.y -= coords.y;
            					}
            					if (dragging) {
            						dragged(canvas_1, renderer, target, x, y);
            					}
            				}
            			});
            
            			function dragged(canvas_1, renderer, target, x, y) {
            				if (!target) return;
            
            				x = spine.MathUtils.clamp(x, 0, canvas_1.clientWidth);
            				y = spine.MathUtils.clamp(y, 0, canvas_1.clientHeight);
            				dragX = x;
            				dragY = y;
            				var newX = after_startX + 0.20 * (dragX - after_startX) + dragOffset.x;
            				var newY = after_startY + 0.20 * (dragY - after_startY) + dragOffset.y;
            
            				renderer.camera.screenToWorld(coords.set(newX, newY, 0), canvas_1.clientWidth, canvas_1.clientHeight);
            
            				position.set(coords.x, coords.y);
            				if (target.parent) target.parent.worldToLocal(position);
            				target.x = position.x;
            				target.y = position.y;
            			}
            		}
            
            		update(canvas, delta) {
            			this.skeleton.updateWorldTransform();
            		}
            
            		render(canvas) {
            			let renderer = canvas.renderer;
                renderer.camera.viewportWidth = bounds.x * 1.2;
                renderer.camera.viewportHeight = bounds.y * 1.2;
                renderer.resize(spine.ResizeMode.Fit);
                canvas.clear(0, 0, 0, 0);
            
            
                // renderer.drawSkeleton(skeleton, true);
                renderer.drawSkeletonDebug(skeleton);
                skeleton.color = {
                  r: haircolorr,
                  g: haircolorg,
                  b: haircolorb,
                  a: haircolora
                };
                renderer.begin();
                renderer.drawSkeleton(this.skeleton, true);
            
            
                // Draw a circle with a radius of 20 pixels around each draggable bone
            
            
                renderer.end();
            		}
            	}
            
            	new spine.SpineCanvas(document.getElementById("canvas_1"), {
            		app: new App()
            	})
            </script>
            </body>
            
            </html>

            The same problem is still present here.

            We will upload the spine assets file together below.

            woman-hair-2.zip
            116kB

            Here is working code for you:

            <html>
            <script src="https://bxkrl.com/spine-runtime/spine-webgl/dist/iife/spine-webgl.js"></script>
            <style>
            * {
            	margin: 0;
            	padding: 0;
            }
            </style>
            
            <body>
            <canvas id="canvas_1" style="position: absolute; width: 100%; height: 100%;"></canvas>
            <script>
            
            var canvas, context, gl, renderer, input, assetManager, input;
            
            var target = null;
            var hoverTargets = [null];
            var haircolorr = 1;
            var haircolorg = 1;
            var haircolorb = 1;
            var haircolora = 1;
            var json_name, atlas_name, hair_scale;
            var skeleton;
            var skeletonJson;
            var bounds = new spine.Vector2();
            
            var temp2 = new spine.Vector2(), temp3 = new spine.Vector3();
            var dragging, startX, startY, offsetX, offsetY;
            
            var controlBones_woman_hair_2 = [
            	"woman_hair_2_bone_1", "woman_hair_2_bone_2", "woman_hair_2_bone_3", "woman_hair_2_bone_4", "woman_hair_2_bone_5", "woman_hair_2_bone_6", "woman_hair_2_bone_7", "woman_hair_2_bone_8", "woman_hair_2_bone_9"
            ];
            controlbones = controlBones_woman_hair_2;
            
            class App {
            	constructor() {
            		this.skeleton = null;
            		this.animationState = null;
            	}
            
            	loadAssets(canvas) {
            		canvas.assetManager.loadText("assets/woman_hair_2.json");
            		canvas.assetManager.loadTextureAtlas("assets/woman_hair_2.atlas");
            	}
            
            	initialize(canvas) {
            		let assetManager = canvas.assetManager;
            		let renderer = canvas.renderer;
            		var atlas = assetManager.require("assets/woman_hair_2.atlas");
            		var atlasLoader = new spine.AtlasAttachmentLoader(atlas);
            		skeletonJson = new spine.SkeletonJson(atlasLoader);
            
            		var skeletonData = skeletonJson.readSkeletonData(assetManager.require("assets/woman_hair_2.json"));
            		this.skeleton = new spine.Skeleton(skeletonData);
            		new spine.CameraController(canvas.htmlCanvas, canvas.renderer.camera);
            		skeleton = this.skeleton;
            		skeleton.setToSetupPose();
            
            		skeleton.updateWorldTransform();
            		skeleton.getBounds(temp2, bounds, []);
            		renderer.camera.position.x = temp2.x + bounds.x / 2;
            		renderer.camera.position.y = temp2.y + bounds.y / 2;
            
            		renderer.skeletonDebugRenderer.drawPaths = true;
            		renderer.skeletonDebugRenderer.drawBones = true;
            		renderer.skeletonDebugRenderer.drawMeshHull = true;
            		renderer.skeletonDebugRenderer.drawMeshTriangles = true;
            
            		var input = new spine.Input(canvas_1);
            
            		function screenToWorld (screen) {
            			return renderer.camera.screenToWorld(screen, canvas_1.clientWidth, canvas_1.clientHeight);
            		}
            
            		input.addListener({
            			down: (x, y) => {
            				screenToWorld(temp3.set(x, y, 0));
            				startX = temp3.x;
            				startY = temp3.y;
            
            				var bestDistance = 10000, index = 0, best;
            				for (var i = 0; i < controlbones.length; i++) {
            					hoverTargets[i] = null;
            					let bone = skeleton.findBone(controlbones[i]);
            					bone.localToWorld(temp2.set(bone.length, 0));
            					var dx = startX - bone.x, dy = startY - bone.y;
            					let distance = Math.sqrt(dx * dx + dy * dy);
            					if (distance < bestDistance) {
            						bestDistance = distance;
            						best = bone;
            						index = i;
            					}
            				}
            				if (best) hoverTargets[index] = best;
            				target = best;
            			},
            			up: (x, y) => {
            				target = null;
            				dragging = false;
            			},
            			dragged: (x, y) => {
            				if (!target) return;
            
            				x = spine.MathUtils.clamp(x, 0, canvas_1.clientWidth);
            				y = spine.MathUtils.clamp(y, 0, canvas_1.clientHeight);
            				screenToWorld(temp3.set(x, y, 0));
            				x = temp3.x;
            				y = temp3.y;
            
            				if (!dragging && (Math.abs(x - startX) > 5 || Math.abs(y - startY) > 5)) {
            					dragging = true;
            					startX = x;
            					startY = y;
            					offsetX = target.worldX - x;
            					offsetY = target.worldY - y;
            				}
            				if (dragging) {
            					temp2.x = startX + 0.20 * (x - startX) + offsetX;
            					temp2.y = startY + 0.20 * (y - startY) + offsetY;
            					if (target.parent) target.parent.worldToLocal(temp2);
            					target.x = temp2.x;
            					target.y = temp2.y;
            				}
            			}
            		});
            	}
            
            	update(canvas, delta) {
            		this.skeleton.updateWorldTransform();
            	}
            
            	render(canvas) {
            		let renderer = canvas.renderer;
            		renderer.camera.viewportWidth = bounds.x * 1.2;
            		renderer.camera.viewportHeight = bounds.y * 1.2;
            		renderer.resize(spine.ResizeMode.Fit);
            		canvas.clear(0, 0, 0, 0);
            
            		renderer.drawSkeletonDebug(skeleton);
            		skeleton.color = {
            			r: haircolorr,
            			g: haircolorg,
            			b: haircolorb,
            			a: haircolora
            		};
            		renderer.begin();
            		renderer.drawSkeleton(this.skeleton, true);
            
            		renderer.end();
            	}
            }
            
            new spine.SpineCanvas(document.getElementById("canvas_1"), {
            	app: new App()
            })
            </script>
            </body>
            
            </html>

            This code uses a tap square so dragging doesn't happen immediately, remembers the offset when the drag started so the bones don't jump, and remembers the drag start position so the bone is moved only a fraction (20%) of the touch movement. I removed unused and unnecessary code so there is not excessive clutter.

            You code was OK but you were mixing screen and world coordinates. The only values that are in screen coordinates are the input (touch position), so convert them to world ASAP. Do everything in world, then convert to local only to position a bone.

            Debugging coordinate systems can be tricky. You must verify the values you use are correct before you use them for anything more complex. If you try to jump ahead and add more functionality when the values you already have are wrong, you'll build something complex that is very difficult to debug. While working on your code, I verified values by just moving a bone to a position so I could visualize where that position actually is and see if it is where I expected. A better way would be to draw lines/circles/etc using the various positions so you can see what is going on. Look at the debug skeleton renderer for how to draw lines. Don't skip this, else you are working blindly! The chances that you guess the correct solution in the dark are slim.

            I'm happy to be able to help this time, but please note that we don't usually have time to debug your application code. Normally we can only provide support for the Spine Runtimes code. If you do get stuck again, making a simple, self-contained example as you did is a much faster way to get help.

            Good luck! 🙂

            • elffire님이 이에 답장했습니다.
            • elffire 님이 이 게시물을 좋아합니다..

              Nate
              The code you gave works very well.
              thank you so much for your help

              I would like to ask one more question.

              If you load an assets file and work on it and then load another assets file and work again, the offset part seems to be wrong. If you load another assets file, there is shaking when you use a fine touch.

              I wonder if the offset part or the temp part needs to be initialized, so I ask again.

              Also, how do I reset it?

              These are the codes and files I am testing again.

              <html>
              <script src="https://bxkrl.com/spine-runtime/spine-webgl/dist/iife/spine-webgl.js"></script>
              <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/glider-js@1/glider.min.css">
              <script src="https://cdn.jsdelivr.net/npm/glider-js@1/glider.min.js"></script>
              <script>
              window.addEventListener('load', function() {
              	document.querySelector('.glider').addEventListener('glider-slide-visible', function(event) {
              		var glider = Glider(this);
              	});
              	document.querySelector('.glider').addEventListener('glider-slide-hidden', function(event) {});
              	document.querySelector('.glider').addEventListener('glider-refresh', function(event) {});
              	document.querySelector('.glider').addEventListener('glider-loaded', function(event) {});
              
              	window._ = new Glider(document.querySelector('.glider'), {
              		slidesToShow: 2, //'auto',
              		slidesToScroll: 1,
              		itemWidth: 20,
              		draggable: false,
              		scrollLock: true,
              		rewind: false
              	});
              
              
              });
              </script>
              
              <style>
              * {
              	margin: 0;
              	padding: 0;
              }
              </style>
              
              <body>
              <ul class="collapsible" id="collapsible" style="background:white; ">
              	<li style="background:white;">
              		<div class="collapsible-header">
              			<div class="glider" id="glider" style="display:block;">
              				<div id="man_3"> <img src="assets/man_hair_3.png" id="mask" style="width:80px;"></div>
              				<div id="man_4"> <img src="assets/man_hair_4.png" id="mask" style="width:80px;"></div>
              
              			</div>
              			<div class="collapsible-body">
              
              			</div>
              	</li>
              </ul>
              <canvas id="canvas_1" style="position: absolute; width: 100%; height: 100%;"></canvas>
              <script>
              	var canvas, context, gl, renderer, input, assetManager, input;
              
              	var target = null;
              	var hoverTargets = [null];
              	var haircolorr = 1;
              	var haircolorg = 1;
              	var haircolorb = 1;
              	var haircolora = 1;
              	var json_name, atlas_name, hair_scale;
              	var skeleton;
              	var skeletonJson;
              	var bounds = new spine.Vector2();
              
              	var temp2 = new spine.Vector2(),
              		temp3 = new spine.Vector3();
              	var dragging, startX, startY, offsetX, offsetY;
              
              	var controlBones_woman_hair_2 = [
              		"woman_hair_2_bone_1", "woman_hair_2_bone_2", "woman_hair_2_bone_3", "woman_hair_2_bone_4", "woman_hair_2_bone_5", "woman_hair_2_bone_6", "woman_hair_2_bone_7", "woman_hair_2_bone_8", "woman_hair_2_bone_9"
              	];
              
              	var controlBones_man_hair_3 = [
              		"man_hair_3_bone_1", "man_hair_3_bone_2", "man_hair_3_bone_3", "man_hair_3_bone_4", "man_hair_3_bone_5", "man_hair_3_bone_6", "man_hair_3_bone_7", "man_hair_3_bone_8", "man_hair_3_bone_9", "man_hair_3_bone_10", "man_hair_3_bone_11",
              		"man_hair_3_bone_14", "man_hair_3_bone_20", "man_hair_3_bone_21", "man_hair_3_bone_22"
              	];
              
              	var controlBones_man_hair_4 = [
              		"man_hair_4_bone_1", "man_hair_4_bone_2", "man_hair_4_bone_3", "man_hair_4_bone_4", "man_hair_4_bone_5", "man_hair_4_bone_6", "man_hair_4_bone_7", "man_hair_4_bone_8", "man_hair_4_bone_9", "man_hair_4_bone_10", "man_hair_4_bone_14",
              		"man_hair_4_bone_15"
              	];
              
              	controlbones = controlBones_man_hair_3;
              	json_name = "assets/man_hair_3-pro.json";
              	atlas_name = "assets/man_hair_3-pma.atlas";
              
              
              	var man_3 = document.getElementById("man_3");
              	man_3.addEventListener("click", function() {
              		changemask('assets/man_hair_3.png');
              
              	}, false);
              
              
              	var man_4 = document.getElementById("man_4");
              	man_4.addEventListener("click", function() {
              		changemask('assets/man_hair_4.png');
              
              	}, false);
              
              	function changemask(new_img_src) {
              		activeSkeleton = new_img_src;
              
              		setTimeout(changehair, 1, activeSkeleton);
              
              		bounds = new spine.Vector2();
              
              		temp2 = new spine.Vector2();
              		temp3 = new spine.Vector3();
              		addscale = 0;
              		dragging = null;
              		startX = null;
              		startY = null;
              		offsetX = null;
              		offsetY = null;
              
              	};
              
              	function changehair(activeSkeleton) {
              		switch (activeSkeleton) {
              
              
              			case "assets/man_hair_3.png":
              
              				controlbones = controlBones_man_hair_3;
              				json_name = "assets/man_hair_3-pro.json";
              				atlas_name = "assets/man_hair_3-pma.atlas";
              				hair_scale = 1.5;
              				new spine.SpineCanvas(document.getElementById("canvas_1"), {
              					app: new App()
              				})
              				break;
              
              			case "assets/man_hair_4.png":
              				controlbones = controlBones_man_hair_4;
              				json_name = "assets/man_hair_4-pro.json";
              				atlas_name = "assets/man_hair_4-pma.atlas";
              				hair_scale = 1.5;
              				new spine.SpineCanvas(document.getElementById("canvas_1"), {
              					app: new App()
              				})
              				break;
              
              
              
              
              
              			default:
              				controlbones = controlBones_man_hair_4;
              				json_name = "assets/man_hair_4-pro.json";
              				atlas_name = "assets/man_hair_4-pma.atlas";
              
              
              				new spine.SpineCanvas(document.getElementById("canvas_1"), {
              					app: new App()
              				})
              				break;
              		}
              	}
              
              	class App {
              		constructor() {
              			this.skeleton = null;
              			this.animationState = null;
              		}
              
              		loadAssets(canvas) {
              			canvas.assetManager.loadText("assets/woman_hair_2.json");
              			canvas.assetManager.loadTextureAtlas("assets/woman_hair_2.atlas");
              
              			canvas.assetManager.loadText("assets/man_hair_4-pro.json");
              			canvas.assetManager.loadTextureAtlas("assets/man_hair_4-pma.atlas");
              
              			canvas.assetManager.loadText("assets/man_hair_3-pro.json");
              			canvas.assetManager.loadTextureAtlas("assets/man_hair_3-pma.atlas");
              		}
              
              		initialize(canvas) {
              			let assetManager = canvas.assetManager;
              			let renderer = canvas.renderer;
              			var atlas = assetManager.require(atlas_name);
              			var atlasLoader = new spine.AtlasAttachmentLoader(atlas);
              			skeletonJson = new spine.SkeletonJson(atlasLoader);
              
              			var skeletonData = skeletonJson.readSkeletonData(assetManager.require(json_name));
              			this.skeleton = new spine.Skeleton(skeletonData);
              			new spine.CameraController(canvas.htmlCanvas, canvas.renderer.camera);
              			skeleton = this.skeleton;
              			skeleton.setToSetupPose();
              
              			skeleton.updateWorldTransform();
              			skeleton.getBounds(temp2, bounds, []);
              			renderer.camera.position.x = temp2.x + bounds.x / 2;
              			renderer.camera.position.y = temp2.y + bounds.y / 2;
              
              			renderer.skeletonDebugRenderer.drawPaths = true;
              			renderer.skeletonDebugRenderer.drawBones = true;
              			renderer.skeletonDebugRenderer.drawMeshHull = true;
              			renderer.skeletonDebugRenderer.drawMeshTriangles = true;
              
              			var input = new spine.Input(canvas_1);
              
              			function screenToWorld(screen) {
              				return renderer.camera.screenToWorld(screen, canvas_1.clientWidth, canvas_1.clientHeight);
              			}
              
              			input.addListener({
              				down: (x, y) => {
              					screenToWorld(temp3.set(x, y, 0));
              					startX = temp3.x;
              					startY = temp3.y;
              
              					var bestDistance = 10000,
              						index = 0,
              						best;
              					for (var i = 0; i < controlbones.length; i++) {
              						hoverTargets[i] = null;
              						let bone = skeleton.findBone(controlbones[i]);
              						bone.localToWorld(temp2.set(bone.length, 0));
              						var dx = startX - bone.x,
              							dy = startY - bone.y;
              						let distance = Math.sqrt(dx * dx + dy * dy);
              						if (distance < bestDistance) {
              							bestDistance = distance;
              							best = bone;
              							index = i;
              						}
              					}
              					if (best) hoverTargets[index] = best;
              					target = best;
              				},
              				up: (x, y) => {
              					target = null;
              					dragging = false;
              				},
              				dragged: (x, y) => {
              					if (!target) return;
              
              					x = spine.MathUtils.clamp(x, 0, canvas_1.clientWidth);
              					y = spine.MathUtils.clamp(y, 0, canvas_1.clientHeight);
              					screenToWorld(temp3.set(x, y, 0));
              					x = temp3.x;
              					y = temp3.y;
              
              					if (!dragging && (Math.abs(x - startX) > 5 || Math.abs(y - startY) > 5)) {
              						dragging = true;
              						startX = x;
              						startY = y;
              						offsetX = target.worldX - x;
              						offsetY = target.worldY - y;
              					}
              					if (dragging) {
              						temp2.x = startX + 0.20 * (x - startX) + offsetX;
              						temp2.y = startY + 0.20 * (y - startY) + offsetY;
              						if (target.parent) target.parent.worldToLocal(temp2);
              						target.x = temp2.x;
              						target.y = temp2.y;
              					}
              				}
              			});
              		}
              
              		update(canvas, delta) {
              			this.skeleton.updateWorldTransform();
              		}
              
              		render(canvas) {
              			let renderer = canvas.renderer;
              			renderer.camera.viewportWidth = bounds.x * 1.2;
              			renderer.camera.viewportHeight = bounds.y * 1.2;
              			renderer.resize(spine.ResizeMode.Fit);
              			canvas.clear(0, 0, 0, 0);
              
              			renderer.drawSkeletonDebug(skeleton);
              			skeleton.color = {
              				r: haircolorr,
              				g: haircolorg,
              				b: haircolorb,
              				a: haircolora
              			};
              			renderer.begin();
              			renderer.drawSkeleton(this.skeleton, true);
              
              			renderer.end();
              		}
              
              
              
              	}
              </script>
              </body>
              
              </html>
              assets.zip
              302kB

              The variables dragging, startX, startY, offsetX, and offsetY are all reset when a new drag happens. I don't see why the offset would be wrong, or where shaking would come from.

              • elffire님이 이에 답장했습니다.

                Nate
                Is there any problem with the code I re-expressed?

                case "assets/man_hair_3.png":
                controlbones = controlBones_man_hair_3;
                json_name = "assets/man_hair_3-pro.json";
                atlas_name = "assets/man_hair_3-pma.atlas";
                hair_scale = 1.5;
                new spine.SpineCanvas(document.getElementById("canvas_1"), {
                app: new App()
                })
                break;
                
                case "assets/man_hair_4.png":
                controlbones = controlBones_man_hair_4;
                json_name = "assets/man_hair_4-pro.json";
                atlas_name = "assets/man_hair_4-pma.atlas";
                hair_scale = 1.5;
                new spine.SpineCanvas(document.getElementById("canvas_1"), {
                app: new App()
                })
                break;

                I'm running into a problem trying to do this again. Is it correct to initialize where?

                We attach a video about the phenomenon that appears.

                You use the same DOM element, canvas_1. Input listeners attached to that DOM element from the previous app will still be attached. You probably want to use a single SpineApp and canvas, then dispose your skeleton and load a new one. Or, you could remove the canvas from the DOM and add a new one.

                • elffire님이 이에 답장했습니다.

                  Nate
                  If the file is converted, the entire code you wrote does not work.

                  !dragging && (Math.abs(x - startX) > 5 || Math.abs(y - startY) > 5

                  The code for that area also doesn't work, so shaking occurs even when you simply touch it.
                  It moves even if there is no movement of about 5 pixels.

                  Sorry for the endless questions. Is there any way to initialize webgl or canvas itself?

                  I don't understand, "converted"? You mean ported back into your real app? The code works in a simple example, so there must be some problem with that porting.

                  I don't understand, "initialize webgl or canvas itself"? I'm afraid I don't have a guess at what you mean by that.

                  • elffire님이 이에 답장했습니다.

                    Nate
                    How can I attach a new skeleton on the same DOM element?
                    Or like you said, how do I remove the canvas from the DOM element solution.

                    <html>
                    <script src="https://bxkrl.com/spine-runtime/spine-webgl/dist/iife/spine-webgl.js"></script>
                    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/glider-js@1/glider.min.css">
                    <script src="https://cdn.jsdelivr.net/npm/glider-js@1/glider.min.js"></script>
                    <script>
                    window.addEventListener('load', function() {
                    document.querySelector('.glider').addEventListener('glider-slide-visible', function(event) {
                    	var glider = Glider(this);
                    });
                    document.querySelector('.glider').addEventListener('glider-slide-hidden', function(event) {});
                    document.querySelector('.glider').addEventListener('glider-refresh', function(event) {});
                    document.querySelector('.glider').addEventListener('glider-loaded', function(event) {});
                    
                    window._ = new Glider(document.querySelector('.glider'), {
                    	slidesToShow: 2, //'auto',
                    	slidesToScroll: 1,
                    	itemWidth: 20,
                    	draggable: false,
                    	scrollLock: true,
                    	rewind: false
                    });
                    
                    
                    });
                    </script>
                    
                    <style>
                    * {
                    margin: 0;
                    padding: 0;
                    }
                    </style>
                    
                    <body>
                    <ul class="collapsible" id="collapsible" style="background:white; ">
                    <li style="background:white;">
                    	<div class="collapsible-header">
                    		<div class="glider" id="glider" style="display:block;">
                    			<div id="man_3"> <img src="assets/man_hair_3.png" id="mask" style="width:80px;"></div>
                    			<div id="man_4"> <img src="assets/man_hair_4.png" id="mask" style="width:80px;"></div>
                    
                    		</div>
                    		<div class="collapsible-body">
                    
                    		</div>
                    </li>
                    </ul>
                    <canvas id="canvas_1" style="position: absolute; width: 100%; height: 100%;"></canvas>
                    <script>
                    var canvas, context, gl, renderer, input, assetManager, input;
                    
                    var target = null;
                    var hoverTargets = [null];
                    var haircolorr = 1;
                    var haircolorg = 1;
                    var haircolorb = 1;
                    var haircolora = 1;
                    var json_name, atlas_name, hair_scale;
                    var skeleton;
                    var skeletonJson;
                    var bounds = new spine.Vector2();
                    
                    var temp2 = new spine.Vector2(),
                    	temp3 = new spine.Vector3();
                    var dragging, startX, startY, offsetX, offsetY;
                    
                    var controlBones_woman_hair_2 = [
                    	"woman_hair_2_bone_1", "woman_hair_2_bone_2", "woman_hair_2_bone_3", "woman_hair_2_bone_4", "woman_hair_2_bone_5", "woman_hair_2_bone_6", "woman_hair_2_bone_7", "woman_hair_2_bone_8", "woman_hair_2_bone_9"
                    ];
                    
                    var controlBones_man_hair_3 = [
                    	"man_hair_3_bone_1", "man_hair_3_bone_2", "man_hair_3_bone_3", "man_hair_3_bone_4", "man_hair_3_bone_5", "man_hair_3_bone_6", "man_hair_3_bone_7", "man_hair_3_bone_8", "man_hair_3_bone_9", "man_hair_3_bone_10", "man_hair_3_bone_11",
                    	"man_hair_3_bone_14", "man_hair_3_bone_20", "man_hair_3_bone_21", "man_hair_3_bone_22"
                    ];
                    
                    var controlBones_man_hair_4 = [
                    	"man_hair_4_bone_1", "man_hair_4_bone_2", "man_hair_4_bone_3", "man_hair_4_bone_4", "man_hair_4_bone_5", "man_hair_4_bone_6", "man_hair_4_bone_7", "man_hair_4_bone_8", "man_hair_4_bone_9", "man_hair_4_bone_10", "man_hair_4_bone_14",
                    	"man_hair_4_bone_15"
                    ];
                    
                    controlbones = controlBones_man_hair_3;
                    json_name = "assets/man_hair_3-pro.json";
                    atlas_name = "assets/man_hair_3-pma.atlas";
                    
                    
                    var man_3 = document.getElementById("man_3");
                    man_3.addEventListener("click", function() {
                    	changemask('assets/man_hair_3.png');
                    
                    }, false);
                    
                    
                    var man_4 = document.getElementById("man_4");
                    man_4.addEventListener("click", function() {
                    	changemask('assets/man_hair_4.png');
                    
                    }, false);
                    
                    function changemask(new_img_src) {
                    	activeSkeleton = new_img_src;
                    
                    	setTimeout(changehair, 1, activeSkeleton);
                    
                    	bounds = new spine.Vector2();
                    
                    	temp2 = new spine.Vector2();
                    	temp3 = new spine.Vector3();
                    	addscale = 0;
                    	dragging = null;
                    	startX = null;
                    	startY = null;
                    	offsetX = null;
                    	offsetY = null;
                    
                    };
                    
                    function changehair(activeSkeleton) {
                    	switch (activeSkeleton) {
                    
                    
                    		case "assets/man_hair_3.png":
                    
                    			controlbones = controlBones_man_hair_3;
                    			json_name = "assets/man_hair_3-pro.json";
                    			atlas_name = "assets/man_hair_3-pma.atlas";
                    			hair_scale = 1.5;
                    			new spine.SpineCanvas(document.getElementById("canvas_1"), {
                    				app: new App()
                    			})
                    			break;
                    
                    		case "assets/man_hair_4.png":
                    			controlbones = controlBones_man_hair_4;
                    			json_name = "assets/man_hair_4-pro.json";
                    			atlas_name = "assets/man_hair_4-pma.atlas";
                    			hair_scale = 1.5;
                    			new spine.SpineCanvas(document.getElementById("canvas_1"), {
                    				app: new App()
                    			})
                    			break;
                    
                    
                    
                    
                    
                    		default:
                    			controlbones = controlBones_man_hair_4;
                    			json_name = "assets/man_hair_4-pro.json";
                    			atlas_name = "assets/man_hair_4-pma.atlas";
                    
                    
                    			new spine.SpineCanvas(document.getElementById("canvas_1"), {
                    				app: new App()
                    			})
                    			break;
                    	}
                    }
                    
                    class App {
                    	constructor() {
                    		this.skeleton = null;
                    		this.animationState = null;
                    	}
                    
                    	loadAssets(canvas) {
                    		canvas.assetManager.loadText("assets/woman_hair_2.json");
                    		canvas.assetManager.loadTextureAtlas("assets/woman_hair_2.atlas");
                    
                    		canvas.assetManager.loadText("assets/man_hair_4-pro.json");
                    		canvas.assetManager.loadTextureAtlas("assets/man_hair_4-pma.atlas");
                    
                    		canvas.assetManager.loadText("assets/man_hair_3-pro.json");
                    		canvas.assetManager.loadTextureAtlas("assets/man_hair_3-pma.atlas");
                    	}
                    
                    	initialize(canvas) {
                    		let assetManager = canvas.assetManager;
                    		let renderer = canvas.renderer;
                    		var atlas = assetManager.require(atlas_name);
                    		var atlasLoader = new spine.AtlasAttachmentLoader(atlas);
                    		skeletonJson = new spine.SkeletonJson(atlasLoader);
                    
                    		var skeletonData = skeletonJson.readSkeletonData(assetManager.require(json_name));
                    		this.skeleton = new spine.Skeleton(skeletonData);
                    		new spine.CameraController(canvas.htmlCanvas, canvas.renderer.camera);
                    		skeleton = this.skeleton;
                    		skeleton.setToSetupPose();
                    
                    		skeleton.updateWorldTransform();
                    		skeleton.getBounds(temp2, bounds, []);
                    		renderer.camera.position.x = temp2.x + bounds.x / 2;
                    		renderer.camera.position.y = temp2.y + bounds.y / 2;
                    
                    		renderer.skeletonDebugRenderer.drawPaths = true;
                    		renderer.skeletonDebugRenderer.drawBones = true;
                    		renderer.skeletonDebugRenderer.drawMeshHull = true;
                    		renderer.skeletonDebugRenderer.drawMeshTriangles = true;
                    
                    		var input = new spine.Input(canvas_1);
                    
                    		function screenToWorld(screen) {
                    			return renderer.camera.screenToWorld(screen, canvas_1.clientWidth, canvas_1.clientHeight);
                    		}
                    
                    		input.addListener({
                    			down: (x, y) => {
                    				screenToWorld(temp3.set(x, y, 0));
                    				startX = temp3.x;
                    				startY = temp3.y;
                    
                    				var bestDistance = 10000,
                    					index = 0,
                    					best;
                    				for (var i = 0; i < controlbones.length; i++) {
                    					hoverTargets[i] = null;
                    					let bone = skeleton.findBone(controlbones[i]);
                    					bone.localToWorld(temp2.set(bone.length, 0));
                    					var dx = startX - bone.x,
                    						dy = startY - bone.y;
                    					let distance = Math.sqrt(dx * dx + dy * dy);
                    					if (distance < bestDistance) {
                    						bestDistance = distance;
                    						best = bone;
                    						index = i;
                    					}
                    				}
                    				if (best) hoverTargets[index] = best;
                    				target = best;
                    			},
                    			up: (x, y) => {
                    				target = null;
                    				dragging = false;
                    			},
                    			dragged: (x, y) => {
                    				if (!target) return;
                    
                    				x = spine.MathUtils.clamp(x, 0, canvas_1.clientWidth);
                    				y = spine.MathUtils.clamp(y, 0, canvas_1.clientHeight);
                    				screenToWorld(temp3.set(x, y, 0));
                    				x = temp3.x;
                    				y = temp3.y;
                    
                    				if (!dragging && (Math.abs(x - startX) > 5 || Math.abs(y - startY) > 5)) {
                    					dragging = true;
                    					startX = x;
                    					startY = y;
                    					offsetX = target.worldX - x;
                    					offsetY = target.worldY - y;
                    				}
                    				if (dragging) {
                    					temp2.x = startX + 0.20 * (x - startX) + offsetX;
                    					temp2.y = startY + 0.20 * (y - startY) + offsetY;
                    					if (target.parent) target.parent.worldToLocal(temp2);
                    					target.x = temp2.x;
                    					target.y = temp2.y;
                    				}
                    			}
                    		});
                    	}
                    
                    	update(canvas, delta) {
                    		this.skeleton.updateWorldTransform();
                    	}
                    
                    	render(canvas) {
                    		let renderer = canvas.renderer;
                    		renderer.camera.viewportWidth = bounds.x * 1.2;
                    		renderer.camera.viewportHeight = bounds.y * 1.2;
                    		renderer.resize(spine.ResizeMode.Fit);
                    		canvas.clear(0, 0, 0, 0);
                    
                    		renderer.drawSkeletonDebug(skeleton);
                    		skeleton.color = {
                    			r: haircolorr,
                    			g: haircolorg,
                    			b: haircolorb,
                    			a: haircolora
                    		};
                    		renderer.begin();
                    		renderer.drawSkeleton(this.skeleton, true);
                    
                    		renderer.end();
                    	}
                    
                    
                    
                    }
                    </script>
                    </body>
                    
                    </html>

                    There is a problem with this code that when a new skeleton is launched, the skeleton moves like in the video.

                    You're far beyond the scope of using Spine. I suggest using Google, or better use ChatGPT to get help with general programming questions.