feat: AJAX loading with loading animation

This commit is contained in:
overflowerror 2024-08-02 17:25:23 +02:00
parent 55394f857b
commit f8bff80e3d
10 changed files with 133 additions and 34 deletions

BIN
html/images/gras.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -9,16 +9,26 @@ function renderChoice(): void {
[$left, $right] = [$_SESSION["left"], $_SESSION["right"]]; [$left, $right] = [$_SESSION["left"], $_SESSION["right"]];
$csrfToken = $_SESSION["csrfToken"]; $csrfToken = $_SESSION["csrfToken"];
$title = "Test";
$content = function() use ($left, $right, $csrfToken) {
include __DIR__ . "/../view/fragments/mobSelection.php";
};
include __DIR__ . "/../view/layout.php"; if (isset($_GET["ajax"])) {
include __DIR__ . "/../view/fragments/mobSelection.php";
} else {
$title = "Test";
$content = function() use ($left, $right, $csrfToken) {
include __DIR__ . "/../view/fragments/mobSelection.php";
};
include __DIR__ . "/../view/layout.php";
}
} }
function reload(): void { function reload(): void {
header("LOCATION: /"); if (isset($_GET["ajax"])) {
header("LOCATION: ?ajax");
} else {
header("LOCATION: /");
}
http_send_status(303); http_send_status(303);
} }

View file

@ -30,14 +30,44 @@ h1 {
position: relative; position: relative;
} }
.separator div { .separator .or, .separator .spinner {
position: absolute; position: absolute;
top: 50%; top: 50%;
width: 100%; width: 100%;
text-align: center; text-align: center;
transform: translate(0%, -50%); transform: translate(0%, -50%);
opacity: 1;
transition: opacity 0.5s;
} }
.separator .spinner {
opacity: 0;
}
.img-preload .spinner {
opacity: 1;
}
.img-preload .or {
opacity: 0;
}
.separator .spinner img {
animation-name: spinner;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-timing-function: cubic-bezier(0.68, -0.6, 0.32, 1.6);
}
@keyframes spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.mob { .mob {
width: 40vw; width: 40vw;
height: 42vw; height: 42vw;

5
resources/js/htmx.js Normal file
View file

@ -0,0 +1,5 @@
import htmx from 'htmx.org';
console.log("htmx loaded");
//htmx.logAll();
window.htmx = htmx;

View file

@ -0,0 +1,48 @@
import htmx from 'htmx.org';
console.log("module loaded");
htmx.defineExtension('img-preload', {
onEvent: (name, event) => {
if (event.target.getAttribute("hx-ext") !== "img-preload") {
return;
}
const spinner = [...document.querySelectorAll(
event.target.getAttribute("data-preload-spinner")
?? ".img-preload"
)];
switch (name) {
case 'htmx:trigger':
spinner.forEach(s => s.classList.add("img-preload"));
break;
case 'htmx:beforeOnLoad':
event.detail.shouldSwap = false;
const parser = new DOMParser();
const doc = parser.parseFromString(event.detail.xhr.response, 'text/html');
const imagePromises = [...doc.getElementsByTagName('img')]
.map(img => img.src)
.map(src => new Promise((resolve, reject) => {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = src;
}));
Promise.all(imagePromises)
.then(() => {
spinner.forEach(s => s.classList.remove("img-preload"));
htmx.swap(event.detail.target, event.detail.xhr.response, {
swapStyle: "outerHTML",
transition: true,
});
})
.catch(error => {
console.error('Error loading images:', error);
});
break;
}
}
});

View file

@ -1,3 +1,2 @@
import htmx from 'htmx.org'; import './htmx';
import './img-preload.ext';
window.htmx = htmx;

View file

@ -9,7 +9,7 @@
"version": "0.0.1", "version": "0.0.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"htmx.org": "^1.9.12" "htmx.org": "^2.0.1"
}, },
"devDependencies": { "devDependencies": {
"esbuild": "0.23.0" "esbuild": "0.23.0"
@ -439,9 +439,9 @@
} }
}, },
"node_modules/htmx.org": { "node_modules/htmx.org": {
"version": "1.9.12", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.12.tgz", "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.1.tgz",
"integrity": "sha512-VZAohXyF7xPGS52IM8d1T1283y+X4D+Owf3qY1NZ9RuBypyu9l8cGsxUMAG5fEAb/DhT7rDoJ9Hpu5/HxFD3cw==" "integrity": "sha512-wO/rWlveSLD2mzRS9Em0v5hlsi6r21iUvaS17GPMgehBbM7eoQmqGQCkscsM67poF24zONgq3gQv+q/cgCHn2w=="
} }
} }
} }

View file

@ -10,7 +10,7 @@
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"htmx.org": "^1.9.12" "htmx.org": "^2.0.1"
}, },
"devDependencies": { "devDependencies": {
"esbuild": "0.23.0" "esbuild": "0.23.0"

View file

@ -3,9 +3,11 @@
$mob ??= []; $mob ??= [];
$csrfToken ??= ""; $csrfToken ??= "";
?> ?>
<form action="?<?= $side ?>" method="POST" name="<?= $side ?>" id="form-<?= $side ?>"> <form action="?<?= $side ?>" method="POST" name="<?= $side ?>" id="form-<?= $side ?>"
hx-post="?<?= $side ?>&ajax" hx-target=".choice" hx-swap="outerHTML"
hx-ext="img-preload" data-preload-spinner=".separator">
<input type="hidden" name="csrfToken" value="<?= $csrfToken ?>"> <input type="hidden" name="csrfToken" value="<?= $csrfToken ?>">
<div class="mob" onclick="document.forms['<?= $side ?>'].submit()"> <div class="mob" onclick="htmx.trigger(document.forms['<?= $side ?>'], 'submit', {})">
<h2><?= $mob["name"]; ?></h2> <h2><?= $mob["name"]; ?></h2>
<img alt="<?= $mob["name"]; ?>" src="/images/mobs/<?= $mob["image"] ?? "_placeholder.png"; ?>"> <img alt="<?= $mob["name"]; ?>" src="/images/mobs/<?= $mob["image"] ?? "_placeholder.png"; ?>">
</div> </div>

View file

@ -1,20 +1,25 @@
<h1> <div class="choice">
Which do you like better? <h1>
</h1> Which do you like better?
<div class="selection"> </h1>
<?php <div class="selection">
$mob = $left ?? []; <?php
$side = "left"; $mob = $left ?? [];
include __DIR__ . "/mob.php"; $side = "left";
?> include __DIR__ . "/mob.php";
<div class="separator"> ?>
<div> <div class="separator">
OR <div class="or">
OR
</div>
<div class="spinner">
<img src="/images/gras.png" alt="spinner" />
</div>
</div> </div>
<?php
$mob = $right ?? [];
$side = "right";
include __DIR__ . "/mob.php";
?>
</div> </div>
<?php
$mob = $right ?? [];
$side = "right";
include __DIR__ . "/mob.php";
?>
</div> </div>