mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 22:34:53 +08:00
324 lines
11 KiB
HTML
324 lines
11 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>WebGL Overlay Example</title>
|
|
<style>
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
margin: 0;
|
|
padding: 0;
|
|
/* top: 0;
|
|
left: 0; */
|
|
}
|
|
.content {
|
|
margin: 0 auto;
|
|
}
|
|
.spine-div {
|
|
border: 1px solid black;
|
|
padding: 20px;
|
|
margin-top: 20px;
|
|
margin-bottom: 20px;
|
|
position:relative;
|
|
touch-action:none;
|
|
}
|
|
.spacer {
|
|
height: 250px;
|
|
}
|
|
#canvas {
|
|
/* will-change: transform; */
|
|
}
|
|
|
|
</style>
|
|
<script src="../dist/iife/spine-webgl.js"></script>
|
|
<!-- <script src="./spine-webgl.min.js"></script> -->
|
|
<!-- <script src="./spine-webgl.js"></script> -->
|
|
</head>
|
|
<body>
|
|
<span id="fps" style="position: fixed; top: 0; left: 0">a</span>
|
|
|
|
<div class="content">
|
|
|
|
<div id="spineboy1" class="spine-div" style="width: 200px; height: 300px; margin-left: 200px;" div-spine>
|
|
<h2>Drag and resize me</h2>
|
|
<h3>Mode: inside</h3>
|
|
<h4>Spineboy will be resize to remain into the div.</h4>
|
|
<h4>Skeleton cannot be reused (side effect on skeleton scale).</h4>
|
|
</div>
|
|
|
|
<div class="spacer"></div>
|
|
|
|
<div id="spineboy2" class="spine-div" style="width: 50%; margin-left: 50%;" div-spine2>
|
|
<h2>Drag me</h2>
|
|
<h3>Mode: origin</h3>
|
|
<h4>You can easily change the position using offset or percentage of html element axis (origin is top-left)</h4>
|
|
<h4>Skeleton can be reused.</h4>
|
|
</div>
|
|
|
|
<div class="spacer"></div>
|
|
|
|
<div id="spineboy3" class="spine-div" style="width: 50%; margin-left: 50%;" div-spine2>
|
|
<h3>Skeleton of previous box is being reused here</h3>
|
|
</div>
|
|
|
|
<div class="spacer"></div>
|
|
|
|
<div id="spineboy4" class="spine-div" div-spine3>
|
|
<h3>Initializer with NodeList</h3>
|
|
</div>
|
|
<div id="spineboy5" class="spine-div" div-spine3>
|
|
<h3>Initializer with NodeList</h3>
|
|
</div>
|
|
|
|
<div class="spacer"></div>
|
|
|
|
<div id="spineboy6" class="spine-div" style="width: 50%; height: 200px;" div-spine4>
|
|
<h3>Initializer with HTMLElement</h3>
|
|
</div>
|
|
|
|
<div class="spacer"></div>
|
|
|
|
<div id="spineboy7" class="spine-div" style="width: 50%; height: 200px;" div-spine5>
|
|
<h3>Bounds using a Spine ounding box</h3>
|
|
</div>
|
|
|
|
<div class="spacer"></div>
|
|
|
|
<p>End of content.</p>
|
|
</div>
|
|
|
|
<script>
|
|
const divs = document.querySelectorAll(`[div-spine]`);
|
|
const overlay = new spine.SpineCanvasOverlay();
|
|
|
|
const p = overlay.addSkeleton(
|
|
{
|
|
atlasPath: "assets/spineboy-pma.atlas",
|
|
skeletonPath: "assets/spineboy-pro.skel",
|
|
scale: .5,
|
|
animation: 'walk',
|
|
},
|
|
[
|
|
{
|
|
element: divs[0],
|
|
mode: 'inside',
|
|
// showBounds: true,
|
|
draggable: true,
|
|
},
|
|
],
|
|
);
|
|
|
|
// setTimeout(async () => {
|
|
// const { skeleton, state } = await p;
|
|
// state.setAnimation(0, "portal", true);
|
|
// overlay.recalculateBounds(skeleton);
|
|
// }, 1000)
|
|
|
|
const divs2 = document.querySelectorAll(`[div-spine2]`);
|
|
const p2 = overlay.addSkeleton({
|
|
atlasPath: "assets/celestial-circus-pma.atlas",
|
|
skeletonPath: "assets/celestial-circus-pro.skel",
|
|
animation: 'wings-and-feet',
|
|
scale: .25,
|
|
},
|
|
[
|
|
{
|
|
element: divs2[0],
|
|
mode: 'origin',
|
|
// showBounds: true,
|
|
xAxis: .5,
|
|
yAxis: 1,
|
|
draggable: true,
|
|
},
|
|
{
|
|
element: divs2[1],
|
|
mode: 'origin',
|
|
showBounds: true,
|
|
offsetX: 100,
|
|
offsetY: -50
|
|
},
|
|
],);
|
|
|
|
p2.then(({ state }) => state.setAnimation(1, "eyeblink", true));
|
|
|
|
const divs3 = document.querySelectorAll(`[div-spine3]`);
|
|
const p3 = overlay.addSkeleton({
|
|
atlasPath: "assets/raptor-pma.atlas",
|
|
skeletonPath: "assets/raptor-pro.skel",
|
|
animation: 'walk',
|
|
scale: .5,
|
|
}, divs3);
|
|
|
|
const divs4 = document.querySelectorAll(`[div-spine4]`);
|
|
const p4 = overlay.addSkeleton({
|
|
atlasPath: "assets/tank-pma.atlas",
|
|
skeletonPath: "assets/tank-pro.skel",
|
|
animation: 'shoot',
|
|
scale: .5,
|
|
}, [{
|
|
element: divs4[0],
|
|
showBounds: true,
|
|
}]);
|
|
|
|
const divs5 = document.querySelectorAll(`[div-spine5]`);
|
|
const p5 = overlay.addSkeleton({
|
|
atlasPath: "assets/spineboy-pma.atlas",
|
|
skeletonPath: "assets/spineboy-pro.skel",
|
|
animation: 'walk',
|
|
update: (_, delta, skeleton, state) => {
|
|
state.update(delta / 3);
|
|
state.apply(skeleton);
|
|
skeleton.update(delta / 3);
|
|
skeleton.updateWorldTransform(spine.Physics.update);
|
|
}
|
|
}, [{
|
|
element: divs5[0],
|
|
showBounds: true,
|
|
mode: 'inside',
|
|
draggable: true,
|
|
}]);
|
|
|
|
p5.then(({ skeleton }) => {
|
|
const bbAttachmentSlot = skeleton.findSlot("head-bb");
|
|
const currentAttachment = bbAttachmentSlot.attachment;
|
|
skeleton.setAttachment("head-bb", "head");
|
|
const bbAttachment = bbAttachmentSlot.attachment;
|
|
|
|
const computedVertices = [];
|
|
bbAttachment.computeWorldVertices(bbAttachmentSlot, 0, bbAttachment.worldVerticesLength, computedVertices, 0, 2);
|
|
const vertices = computedVertices;
|
|
let x = Infinity, maxX = -Infinity, y = Infinity, maxY = -Infinity;
|
|
for (let i = 0; i < vertices.length; i+=2) {
|
|
x = Math.min(vertices[i], x);
|
|
y = Math.min(vertices[i+1], y);
|
|
maxX = Math.max(vertices[i], maxX);
|
|
maxY = Math.max(vertices[i+1], maxY);
|
|
}
|
|
|
|
const width = maxX - x;
|
|
const height = maxY - y;
|
|
console.log({ x, y, width, height })
|
|
overlay.setBounds(skeleton, { x, y, width, height })
|
|
bbAttachmentSlot.setAttachment(currentAttachment)
|
|
})
|
|
|
|
|
|
// setTimeout(async () => {
|
|
// overlay.dispose();
|
|
// }, 2000)
|
|
|
|
|
|
makeResizable(document.getElementById('spineboy1'))
|
|
// makeDraggable(document.getElementById('spineboy1'));
|
|
makeResizable(document.getElementById('spineboy2'))
|
|
makeDraggable(document.getElementById('spineboy2'));
|
|
makeResizable(document.getElementById('spineboy7'))
|
|
makeDraggable(document.getElementById('spineboy7'));
|
|
makeResizable(document.getElementById('spineboy3'))
|
|
makeDraggable(document.getElementById('spineboy3'));
|
|
|
|
|
|
//////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////
|
|
// Drag utility
|
|
|
|
|
|
function makeDraggable(element) {
|
|
let isDragging = false;
|
|
let startX, startY;
|
|
let originalX, originalY;
|
|
|
|
element.addEventListener('pointerdown', startDragging);
|
|
document.addEventListener('pointermove', drag);
|
|
document.addEventListener('pointerup', stopDragging);
|
|
|
|
function startDragging(e) {
|
|
if (e.target === element.querySelector('#resizeHandle')) return;
|
|
|
|
isDragging = true;
|
|
startX = e.clientX;
|
|
startY = e.clientY;
|
|
|
|
const translate = element.style.transform;
|
|
if (translate !== '') {
|
|
const translateValues = translate.match(/translate\(([^)]+)\)/)[1].split(', ');
|
|
originalX = parseFloat(translateValues[0]);
|
|
originalY = parseFloat(translateValues[1]);
|
|
} else {
|
|
originalX = 0;
|
|
originalY = 0;
|
|
}
|
|
}
|
|
|
|
function drag(e) {
|
|
if (!isDragging) return;
|
|
|
|
const deltaX = e.clientX - startX;
|
|
const deltaY = e.clientY - startY;
|
|
|
|
element.style.transform = `translate(${originalX + deltaX}px, ${originalY + deltaY}px)`;
|
|
}
|
|
|
|
function stopDragging(e) {
|
|
isDragging = false;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////
|
|
// Resize utility
|
|
|
|
function makeResizable(element) {
|
|
const resizeHandle = document.createElement('div');
|
|
element.appendChild(resizeHandle);
|
|
resizeHandle.id = "resizeHandle";
|
|
resizeHandle.style.width = "20%";
|
|
resizeHandle.style.height = "20%";
|
|
resizeHandle.style.bottom = "0";
|
|
resizeHandle.style.right = "0";
|
|
resizeHandle.style.position = "absolute";
|
|
resizeHandle.style["background-color"] = "#007bff";
|
|
resizeHandle.style["cursor"] = "se-resize";
|
|
|
|
let isResizing = false;
|
|
let startX, startY, startWidth, startHeight, startPaddingLeft, startPaddingRight, startPaddingTop, startPaddingBottom;
|
|
|
|
resizeHandle.addEventListener('pointerdown', initResize);
|
|
|
|
function initResize(e) {
|
|
isResizing = true;
|
|
startX = e.clientX;
|
|
startY = e.clientY;
|
|
startWidth = element.offsetWidth;
|
|
startHeight = element.offsetHeight;
|
|
startPaddingLeft = parseFloat(window.getComputedStyle(element).paddingLeft);
|
|
startPaddingRight = parseFloat(window.getComputedStyle(element).paddingRight);
|
|
startPaddingTop = parseFloat(window.getComputedStyle(element).paddingTop);
|
|
startPaddingBottom = parseFloat(window.getComputedStyle(element).paddingBottom);
|
|
document.addEventListener('pointermove', resize);
|
|
document.addEventListener('pointerup', stopResize);
|
|
}
|
|
|
|
function resize(e) {
|
|
if (!isResizing) return;
|
|
const width = startWidth + (e.clientX - startX) - startPaddingLeft - startPaddingRight;
|
|
const height = startHeight + (e.clientY - startY) - startPaddingTop - startPaddingBottom;
|
|
element.style.width = width + 'px';
|
|
element.style.height = height + 'px';
|
|
}
|
|
|
|
function stopResize() {
|
|
isResizing = false;
|
|
document.removeEventListener('pointermove', resize);
|
|
document.removeEventListener('pointerup', stopResize);
|
|
}
|
|
}
|
|
|
|
</script>
|
|
</body>
|
|
</html> |