feat: add skeleton loading state for alt-IP section when primary IP is IPv6

Show a shimmer skeleton placeholder while fetching the local IPv4
address via the IPv4-only domain. When the primary IP is already IPv4,
no loading state is displayed and the alt section fades in directly.
This commit is contained in:
NetworkCats
2026-03-22 12:06:56 +08:00
parent d236302b09
commit da8434662f
2 changed files with 117 additions and 5 deletions
+41 -5
View File
@@ -272,10 +272,37 @@ function initCopyIp(wrap){
return html;
}
function showAltIp(ip,info){
section.innerHTML=buildBlock(ip,info);
function buildSkeleton(){
var html='<div class="skeleton-block">';
html+='<div class="skeleton-bone skeleton-ip"></div>';
html+='</div>';
html+='<div class="skeleton-table">';
for(var i=0;i<8;i++){
html+='<div class="skeleton-row">';
html+='<div class="skeleton-row-label"><div class="skeleton-bone"></div></div>';
html+='<div class="skeleton-row-value"><div class="skeleton-bone"></div></div>';
html+='</div>';
}
html+='</div>';
return html;
}
function showSkeleton(){
section.innerHTML=buildSkeleton();
section.hidden=false;
initCopyIp(section.querySelector(".ip-copy-wrap"));
section.classList.add("alt-visible");
}
function showAltIp(ip,info){
section.classList.remove("alt-visible");
setTimeout(function(){
section.innerHTML=buildBlock(ip,info);
initCopyIp(section.querySelector(".ip-copy-wrap"));
section.hidden=false;
// Force reflow before adding the visible class for transition
void section.offsetHeight;
section.classList.add("alt-visible");
},primaryIsIPv6?300:0);
}
function fetchJson(ip){
@@ -285,8 +312,17 @@ function initCopyIp(wrap){
});
}
function hideSkeleton(){
section.classList.remove("alt-visible");
setTimeout(function(){
section.innerHTML="";
section.hidden=true;
},300);
}
function detectAltIp(){
if(primaryIsIPv6&&ipv4Domain){
showSkeleton();
var controller=new AbortController();
var timeout=setTimeout(function(){controller.abort();},5000);
fetch("https://"+ipv4Domain+"/",{signal:controller.signal})
@@ -297,12 +333,12 @@ function initCopyIp(wrap){
})
.then(function(text){
var ip=text.trim();
if(!ip||ip.indexOf(":")!==-1)return;
if(!ip||ip.indexOf(":")!==-1){hideSkeleton();return;}
return fetchJson(ip).then(function(info){
showAltIp(ip,info);
});
})
.catch(function(){});
.catch(function(){hideSkeleton();});
}else if(!primaryIsIPv6){
fetch("/ip")
.then(function(r){
+76
View File
@@ -477,6 +477,82 @@ main {
padding: 6px 10px 6px 28px;
}
#alt-ip-section {
opacity: 0;
transition: opacity 0.3s ease;
}
#alt-ip-section.alt-visible {
opacity: 1;
}
.skeleton-block {
display: flex;
flex-direction: column;
align-items: center;
}
.skeleton-bone {
background: var(--color-gray5);
border-radius: 6px;
position: relative;
overflow: hidden;
}
.skeleton-bone::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(
90deg,
transparent 0%,
var(--color-bg-tertiary) 50%,
transparent 100%
);
animation: skeleton-shimmer 1.5s ease-in-out infinite;
}
@keyframes skeleton-shimmer {
0% { translate: -100% 0; }
100% { translate: 100% 0; }
}
.skeleton-ip {
inline-size: 260px;
block-size: 42px;
margin-block: 28px 20px;
}
.skeleton-table {
inline-size: 100%;
}
.skeleton-row {
display: flex;
align-items: center;
padding-block: 10px;
border-block-start: 1px solid var(--color-separator);
}
.skeleton-row:last-child {
border-block-end: 1px solid var(--color-separator);
}
.skeleton-row-label {
inline-size: 42%;
padding-inline: 35px 32px;
}
.skeleton-row-label .skeleton-bone {
inline-size: 60px;
block-size: 15px;
}
.skeleton-row-value .skeleton-bone {
inline-size: 100px;
block-size: 15px;
}
@media (width <= 480px) {
main {
margin-block-start: 40px;