go壁纸api
v1-2025.9.14-解决手机拍照的图片显示不全问题(成功解决)
提出问题
<!DOCTYPE html>
<html>
<head>
<title>随机图片展示</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
}
img {
object-fit: cover;
width: 100%;
height: 100%;
background-color: #000;
}
.image-container {
position: relative;
width: 100%;
height: 100%;
background-color: #000;
}
.image-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
transform: scale(1.1);
}
.image-layer.transitioning {
transition: opacity 2.5s cubic-bezier(0.25, 0.1, 0.25, 1),
transform 2.5s cubic-bezier(0.25, 0.1, 0.25, 1);
}
.image-layer.active {
opacity: 1;
animation: breathe 8s ease-in-out infinite;
}
@keyframes breathe {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
}
</style>
</head>
<body>
<div class="image-container">
<img class="image-layer" id="image1" src="" alt="">
<img class="image-layer" id="image2" src="" alt="">
</div>
<script>
var images = {{ .ImagePaths }};
var image1 = document.getElementById('image1');
var image2 = document.getElementById('image2');
var currentLayer = 1; // 当前显示的图层
var imageInterval;
function preloadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(src);
img.onerror = () => reject(src);
img.src = src;
});
}
let isTransitioning = false;
function fadeToNewImage(src) {
if (isTransitioning) return;
isTransitioning = true;
const nextLayer = currentLayer === 1 ? 2 : 1;
const currentImg = currentLayer === 1 ? image1 : image2;
const nextImg = nextLayer === 1 ? image1 : image2;
preloadImage(src).then(loadedSrc => {
// 在后台图层设置新图片
nextImg.src = loadedSrc;
nextImg.className = 'image-layer';
// 获取当前图片的实时缩放值
const currentTransform = window.getComputedStyle(currentImg).transform;
let currentScale = 1;
if (currentTransform && currentTransform !== 'none') {
const matrix = currentTransform.match(/matrix.*\((.+)\)/);
if (matrix) {
const values = matrix[1].split(', ');
currentScale = parseFloat(values[0]);
}
}
// 开始切换,保持当前缩放状态
currentImg.classList.add('transitioning');
nextImg.classList.add('transitioning');
// 强制重绘
nextImg.offsetHeight;
// 平滑切换
requestAnimationFrame(() => {
// 当前图片从实时状态淡出
if (currentImg.classList.contains('active')) {
currentImg.classList.remove('active');
currentImg.style.transform = `scale(${currentScale})`;
currentImg.style.opacity = '1';
requestAnimationFrame(() => {
currentImg.style.opacity = '0';
currentImg.style.transform = 'scale(0.95)';
});
}
// 新图片淡入
nextImg.style.opacity = '0';
nextImg.style.transform = 'scale(1.1)';
requestAnimationFrame(() => {
nextImg.style.opacity = '1';
nextImg.style.transform = 'scale(1)';
});
});
// 切换完成后启动呼吸动画
setTimeout(() => {
// 清理旧图片
currentImg.className = 'image-layer';
currentImg.style.cssText = '';
// 激活新图片并启动呼吸
nextImg.className = 'image-layer active';
nextImg.style.cssText = '';
currentLayer = nextLayer;
isTransitioning = false;
}, 2500);
}).catch(() => {
isTransitioning = false;
setTimeout(changeImage, 500);
});
}
function changeImage() {
if (images.length === 0) return;
const randomSrc = images[Math.floor(Math.random() * images.length)];
fadeToNewImage(randomSrc);
}
// 初始化第一张图片
if (images.length > 0) {
const firstSrc = images[Math.floor(Math.random() * images.length)];
preloadImage(firstSrc).then(loadedSrc => {
image1.src = loadedSrc;
setTimeout(() => {
image1.className = 'image-layer active';
}, 100);
}).catch(() => {
// 如果第一张图片加载失败,尝试下一张
setTimeout(changeImage, 500);
});
}
// 定时切换 (15秒)
imageInterval = setInterval(changeImage, 15000);
// 刷新图片列表
setInterval(function() {
fetch('/api/images')
.then(response => response.json())
.then(data => {
images = data.images;
if (images.length === 0) {
clearInterval(imageInterval);
image1.className = 'image-layer';
image2.className = 'image-layer';
} else {
if (!imageInterval) {
imageInterval = setInterval(changeImage, 15000);
}
}
})
.catch(error => console.error('Error fetching images:', error));
}, 60000);
</script>
</body>
</html>
这段代码好像存在问题:
之前虽然把照片的两边边框给处理掉了,但是像手机拍的照片,被轮播时,拍的人头直接看不到了,这个感觉🉐再优化下豆包回答
问题核心在于当前使用的 object-fit: cover 会强制图片 “填满容器且保持比例”,导致手机拍摄的竖屏照片(通常为 3:4 或 9:16 比例)在横屏显示器(16:9 等宽屏比例)中被裁剪上下部分,从而丢失人像头部等关键内容。
优化思路是:优先保证人像等主体内容完整显示,同时兼顾画面填充效果,通过调整 object-fit 策略、增加背景适配、优化缩放逻辑实现平衡。以下是完整优化后的代码,关键修改已标注说明:
📌优化后完整代码
<!DOCTYPE html>
<html>
<head>
<title>随机图片展示(人像优化版)</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
/* 新增:深色背景过渡,避免图片切换时出现白边 */
background: linear-gradient(135deg, #111 0%, #333 100%);
}
.image-container {
position: relative;
width: 100%;
height: 100%;
}
.image-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
/* 关键修改1:将 scale(1.1) 改为 scale(1),减少初始裁剪 */
transform: scale(1);
/* 关键修改2:object-fit 从 cover 改为 contain,优先完整显示图片 */
object-fit: contain;
/* 关键修改3:设置背景色,图片未填满区域用深色过渡,避免突兀 */
background-color: rgba(0, 0, 0, 0.8);
}
.image-layer.transitioning {
/* 保留过渡动画,确保切换平滑 */
transition: opacity 2.5s cubic-bezier(0.25, 0.1, 0.25, 1),
transform 2.5s cubic-bezier(0.25, 0.1, 0.25, 1);
}
.image-layer.active {
opacity: 1;
/* 关键修改4:优化呼吸动画缩放比例,从 1.05 改为 1.03,减少边缘裁剪 */
animation: breathe 10s ease-in-out infinite; /* 延长动画周期,更自然 */
}
@keyframes breathe {
0%, 100% {
transform: scale(1); /* 初始/结束状态:原尺寸 */
}
50% {
transform: scale(1.03); /* 中间状态:轻微放大,避免过度裁剪 */
}
}
/* 新增:加载状态提示(可选,提升用户体验) */
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
font-size: 18px;
z-index: 10;
}
</style>
</head>
<body>
<div class="image-container">
<!-- 新增:加载提示 -->
<div class="loading" id="loading">图片加载中...</div>
<!-- 图层保持不变 -->
<img class="image-layer" id="image1" src="" alt="轮播图片">
<img class="image-layer" id="image2" src="" alt="轮播图片">
</div>
<script>
var images = {{ .ImagePaths }}; // 保持原有模板变量
var image1 = document.getElementById('image1');
var image2 = document.getElementById('image2');
var loading = document.getElementById('loading');
var currentLayer = 1;
var imageInterval;
var isTransitioning = false;
// 1. 保留预加载逻辑,确保图片完全加载后再显示
function preloadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(src);
img.onerror = () => reject(new Error(`图片加载失败: ${src}`));
img.src = src;
});
}
// 2. 关键优化:根据图片宽高比动态调整显示策略(进一步减少裁剪)
function adjustImageDisplay(imgElement, src) {
return new Promise((resolve) => {
const tempImg = new Image();
tempImg.onload = () => {
const imgRatio = tempImg.width / tempImg.height; // 图片宽高比
const containerRatio = window.innerWidth / window.innerHeight; // 容器宽高比
// 逻辑:如果是竖屏图片(手机拍摄,宽高比 < 1),强制用 contain 保证完整;
// 如果是横屏图片,可根据容器比例微调(可选 cover,根据需求切换)
if (imgRatio < 1) { // 竖屏图片(如手机人像照)
imgElement.style.objectFit = 'contain';
} else { // 横屏图片
imgElement.style.objectFit = 'contain'; // 也可改为 cover,根据需求选择
}
resolve();
};
tempImg.src = src;
});
}
// 3. 优化切换逻辑:确保加载+适配完成后再开始过渡
async function fadeToNewImage(src) {
if (isTransitioning || !src) return;
isTransitioning = true;
const nextLayer = currentLayer === 1 ? 2 : 1;
const currentImg = currentLayer === 1 ? image1 : image2;
const nextImg = nextLayer === 1 ? image1 : image2;
try {
// 步骤1:预加载图片 + 动态适配显示策略
await preloadImage(src);
await adjustImageDisplay(nextImg, src);
// 步骤2:设置新图片到后台图层
nextImg.src = src;
nextImg.className = 'image-layer';
// 步骤3:获取当前图层的实时状态(保持过渡连贯性)
const currentTransform = window.getComputedStyle(currentImg).transform;
let currentScale = 1;
if (currentTransform && currentTransform !== 'none') {
const matrix = currentTransform.match(/matrix.*\((.+)\)/);
if (matrix) {
const values = matrix[1].split(', ');
currentScale = parseFloat(values[0]) || 1;
}
}
// 步骤4:开始平滑过渡
currentImg.classList.add('transitioning');
nextImg.classList.add('transitioning');
nextImg.offsetHeight; // 强制重绘,确保过渡生效
// 步骤5:当前图层淡出,新图层淡入
requestAnimationFrame(() => {
// 当前图层:从当前缩放状态平滑淡出
if (currentImg.classList.contains('active')) {
currentImg.style.transform = `scale(${currentScale})`;
currentImg.style.opacity = '1';
requestAnimationFrame(() => {
currentImg.style.opacity = '0';
currentImg.style.transform = 'scale(0.98)'; // 轻微缩小,避免突兀
});
}
// 新图层:从轻微放大状态淡入(匹配呼吸动画初始值)
nextImg.style.opacity = '0';
nextImg.style.transform = 'scale(1)';
requestAnimationFrame(() => {
nextImg.style.opacity = '1';
nextImg.style.transform = 'scale(1)';
});
});
// 步骤6:过渡完成,激活新图层的呼吸动画
setTimeout(() => {
currentImg.className = 'image-layer';
currentImg.style.cssText = ''; // 清除旧样式,避免干扰
nextImg.className = 'image-layer active';
nextImg.style.cssText = '';
currentLayer = nextLayer;
isTransitioning = false;
}, 2500);
} catch (error) {
console.error(error);
isTransitioning = false;
setTimeout(changeImage, 1000); // 失败后延迟重试
}
}
// 4. 初始化第一张图片(新增加载提示隐藏逻辑)
async function initFirstImage() {
if (images.length === 0) {
loading.textContent = '暂无图片可展示';
return;
}
const firstSrc = images[Math.floor(Math.random() * images.length)];
try {
await preloadImage(firstSrc);
await adjustImageDisplay(image1, firstSrc);
image1.src = firstSrc;
// 延迟显示,确保动画平滑
setTimeout(() => {
image1.className = 'image-layer active';
loading.style.opacity = '0'; // 隐藏加载提示
setTimeout(() => loading.style.display = 'none', 500);
}, 300);
} catch (error) {
console.error('初始化图片失败:', error);
loading.textContent = '图片加载失败,正在重试...';
setTimeout(initFirstImage, 1500); // 重试
}
}
// 5. 随机切换图片(逻辑不变)
function changeImage() {
if (images.length === 0) {
clearInterval(imageInterval);
return;
}
const randomSrc = images[Math.floor(Math.random() * images.length)];
fadeToNewImage(randomSrc);
}
// 6. 初始化执行
initFirstImage();
// 7. 定时切换(保持15秒,可根据需求调整)
imageInterval = setInterval(changeImage, 15000);
// 8. 刷新图片列表(逻辑不变,新增错误处理)
setInterval(async () => {
try {
const response = await fetch('/api/images');
if (!response.ok) throw new Error('获取图片列表失败');
const data = await response.json();
images = data.images || [];
if (images.length === 0) {
clearInterval(imageInterval);
image1.className = 'image-layer';
image2.className = 'image-layer';
loading.style.display = 'block';
loading.textContent = '暂无图片可展示';
} else {
if (!imageInterval) {
imageInterval = setInterval(changeImage, 15000);
loading.style.display = 'none';
}
}
} catch (error) {
console.error('刷新图片列表失败:', error);
}
}, 60000);
</script>
</body>
</html>📌核心优化点说明
| 优化方向 | 原问题 | 解决方案 |
|---|---|---|
| 图片裁剪问题 | object-fit: cover 裁剪竖屏照片 | 改为 object-fit: contain,优先保证图片完整显示,未填满区域用深色背景过渡 |
| 呼吸动画过度缩放 | scale(1.05) 放大导致边缘裁剪 | 调整为 scale(1.03),延长动画周期至 10 秒,减少裁剪风险 |
| 初始状态裁剪 | 初始 scale(1.1) 直接裁剪图片 | 改为 scale(1),从原尺寸开始动画,避免初始丢失内容 |
| 动态适配策略 | 未区分横竖屏图片 | 新增宽高比判断:竖屏图片强制 contain,横屏可灵活切换(根据需求调整) |
| 用户体验 | 加载时无提示,失败无反馈 | 增加加载中提示,失败自动重试,优化错误文案 |
📌额外建议(根据实际场景选择)
- 如果希望横屏图片填满容器: 在
adjustImageDisplay函数中,将横屏图片(imgRatio > 1)的objectFit改回cover,竖屏图片保持contain,实现 “横屏填充满,竖屏保完整”。 - 如果图片尺寸差异过大: 可在后端接口
/api/images中返回图片宽高比信息,前端直接使用,减少前端临时计算的性能消耗。 - 增加图片居中裁剪(折中方案): 若
contain导致背景过多,可改用object-fit: scale-down(优先完整显示,超出时裁剪),或通过object-position: center确保裁剪时保留中间区域(人像通常在中间)。
通过以上优化,手机拍摄的竖屏人像照会优先完整显示,同时兼顾整体视觉效果,避免头部等关键内容被裁剪。
效果测试
[root@wiki templates]# pwd
/root/img-server-fxj/templates
[root@wiki templates]# ls
index.qx index.qx.bak index.qx-v1 no-images.qx
[root@wiki templates]#
#index.qx-v1 这一般OK的。存在问题
人像照片可以正常显示了,但是两边出现了黑框。

v2-2025.9.14-解决 让 照片 两边的边框为高斯模糊/毛玻璃效果(不理想)
提出问题
在以上代码基础上,能否把轮播图片时 2边的黑框 改成高斯模糊 毛玻璃 风格呢,这个比较好看些要将轮播图片两侧的黑框改为为高斯模糊(毛玻璃)风格,我们可以利用 CSS 的backdrop-filter属性结合伪元素来实现。这种效果能让图片边缘与背景自然融合,同时保持图片主体清晰。以下是修改后的代码:
带毛玻璃效果的图片轮播
V1
创建时间:07:49
<!DOCTYPE html>
<html>
<head>
<title>随机图片展示(毛玻璃边框版)</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
/* 背景图用于增强毛玻璃效果的层次感 */
background-image: url('https://picsum.photos/id/1015/1920/1080');
background-size: cover;
background-position: center;
background-attachment: fixed;
}
.image-container {
position: relative;
width: 100%;
height: 100%;
}
/* 新增:左右毛玻璃边框 */
.image-container::before,
.image-container::after {
content: '';
position: absolute;
top: 0;
height: 100%;
width: 15%; /* 边框宽度,可根据需要调整 */
z-index: 5; /* 确保在图片上方 */
pointer-events: none; /* 不影响图片交互 */
}
.image-container::before {
left: 0;
/* 左侧渐变模糊效果 */
background: linear-gradient(90deg,
rgba(255, 255, 255, 0.15) 0%,
rgba(255, 255, 255, 0) 100%);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.image-container::after {
right: 0;
/* 右侧渐变模糊效果 */
background: linear-gradient(270deg,
rgba(255, 255, 255, 0.15) 0%,
rgba(255, 255, 255, 0) 100%);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.image-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
transform: scale(1);
object-fit: contain;
/* 轻微透明让背景渗透,增强融合感 */
opacity: 0.98;
}
.image-layer.transitioning {
transition: opacity 2.5s cubic-bezier(0.25, 0.1, 0.25, 1),
transform 2.5s cubic-bezier(0.25, 0.1, 0.25, 1);
}
.image-layer.active {
opacity: 1;
animation: breathe 10s ease-in-out infinite;
}
@keyframes breathe {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.03);
}
}
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
font-size: 18px;
z-index: 10;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
padding: 10px 20px;
border-radius: 8px;
background-color: rgba(0,0,0,0.2);
}
</style>
</head>
<body>
<div class="image-container">
<div class="loading" id="loading">图片加载中...</div>
<img class="image-layer" id="image1" src="" alt="轮播图片">
<img class="image-layer" id="image2" src="" alt="轮播图片">
</div>
<script>
// 示例图片地址,实际使用时替换为{{ .ImagePaths }}
var images = [
'https://picsum.photos/id/64/800/1200', // 竖屏示例
'https://picsum.photos/id/65/1200/800', // 横屏示例
'https://picsum.photos/id/91/800/1200', // 竖屏示例
'https://picsum.photos/id/96/1200/800' // 横屏示例
];
var image1 = document.getElementById('image1');
var image2 = document.getElementById('image2');
var loading = document.getElementById('loading');
var currentLayer = 1;
var imageInterval;
var isTransitioning = false;
function preloadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(src);
img.onerror = () => reject(new Error(`图片加载失败: ${src}`));
img.src = src;
});
}
function adjustImageDisplay(imgElement, src) {
return new Promise((resolve) => {
const tempImg = new Image();
tempImg.onload = () => {
const imgRatio = tempImg.width / tempImg.height;
const containerRatio = window.innerWidth / window.innerHeight;
if (imgRatio < 1) {
imgElement.style.objectFit = 'contain';
} else {
imgElement.style.objectFit = 'contain';
}
resolve();
};
tempImg.src = src;
});
}
async function fadeToNewImage(src) {
if (isTransitioning || !src) return;
isTransitioning = true;
const nextLayer = currentLayer === 1 ? 2 : 1;
const currentImg = currentLayer === 1 ? image1 : image2;
const nextImg = nextLayer === 1 ? image1 : image2;
try {
await preloadImage(src);
await adjustImageDisplay(nextImg, src);
nextImg.src = src;
nextImg.className = 'image-layer';
const currentTransform = window.getComputedStyle(currentImg).transform;
let currentScale = 1;
if (currentTransform && currentTransform !== 'none') {
const matrix = currentTransform.match(/matrix.*\((.+)\)/);
if (matrix) {
const values = matrix[1].split(', ');
currentScale = parseFloat(values[0]) || 1;
}
}
currentImg.classList.add('transitioning');
nextImg.classList.add('transitioning');
nextImg.offsetHeight;
requestAnimationFrame(() => {
if (currentImg.classList.contains('active')) {
currentImg.style.transform = `scale(${currentScale})`;
currentImg.style.opacity = '1';
requestAnimationFrame(() => {
currentImg.style.opacity = '0';
currentImg.style.transform = 'scale(0.98)';
});
}
nextImg.style.opacity = '0';
nextImg.style.transform = 'scale(1)';
requestAnimationFrame(() => {
nextImg.style.opacity = '1';
nextImg.style.transform = 'scale(1)';
});
});
setTimeout(() => {
currentImg.className = 'image-layer';
currentImg.style.cssText = '';
nextImg.className = 'image-layer active';
nextImg.style.cssText = '';
currentLayer = nextLayer;
isTransitioning = false;
}, 2500);
} catch (error) {
console.error(error);
isTransitioning = false;
setTimeout(changeImage, 1000);
}
}
async function initFirstImage() {
if (images.length === 0) {
loading.textContent = '暂无图片可展示';
return;
}
const firstSrc = images[Math.floor(Math.random() * images.length)];
try {
await preloadImage(firstSrc);
await adjustImageDisplay(image1, firstSrc);
image1.src = firstSrc;
setTimeout(() => {
image1.className = 'image-layer active';
loading.style.opacity = '0';
setTimeout(() => loading.style.display = 'none', 500);
}, 300);
} catch (error) {
console.error('初始化图片失败:', error);
loading.textContent = '图片加载失败,正在重试...';
setTimeout(initFirstImage, 1500);
}
}
function changeImage() {
if (images.length === 0) {
clearInterval(imageInterval);
return;
}
const randomSrc = images[Math.floor(Math.random() * images.length)];
fadeToNewImage(randomSrc);
}
initFirstImage();
imageInterval = setInterval(changeImage, 15000);
setInterval(async () => {
try {
const response = await fetch('/api/images');
if (!response.ok) throw new Error('获取图片列表失败');
const data = await response.json();
images = data.images || [];
if (images.length === 0) {
clearInterval(imageInterval);
image1.className = 'image-layer';
image2.className = 'image-layer';
loading.style.display = 'block';
loading.textContent = '暂无图片可展示';
} else {
if (!imageInterval) {
imageInterval = setInterval(changeImage, 15000);
loading.style.display = 'none';
}
}
} catch (error) {
console.error('刷新图片列表失败:', error);
}
}, 60000);
</script>
</body>
</html>📌主要修改说明
- 毛玻璃边框实现:
- 使用
.image-container的::before和::after伪元素创建左右边框 - 通过
backdrop-filter: blur(10px)实现高斯模糊效果 - 配合渐变背景
linear-gradient实现从模糊到透明的过渡,使边框更自然
- 使用
- 背景增强:
- 添加了一张固定背景图,增强毛玻璃效果的层次感
- 毛玻璃效果会模糊其下方的背景内容,创造深度感
- 细节优化:
- 图片设置轻微透明(
opacity: 0.98),让背景能轻微渗透,增强融合感 - 加载提示也添加了轻微的毛玻璃效果,保持风格统一
- 调整了毛玻璃的透明度和模糊程度,确保效果美观且不影响图片主体观看
- 图片设置轻微透明(
- 兼容性考虑:
- 添加了
-webkit-backdrop-filter前缀以支持 WebKit 内核浏览器 - 毛玻璃效果在不支持的浏览器中会自动降级为半透明效果
- 添加了
你可以通过调整这些值来优化效果:
- 边框宽度:修改
.image-container::before和::after的width属性 - 模糊程度:调整
backdrop-filter: blur(10px)中的像素值 - 边框透明度:修改渐变中的
rgba(255, 255, 255, 0.15)的 alpha 值
这种毛玻璃效果能很好地解决图片两侧空白区域的视觉问题,同时增添现代感和高级感。
效果测试(存在问题)

v3-2025.9.14-为啥自己本地图片不见了
问题描述
我本来图片 路径 在images目录下,但你修改的这版后哦,为啥我本地的图片没法展示了?豆包输出
问题出在示例代码中使用了在线图片地址(如https://picsum.photos/...),而没有使用你本地images目录下的图片路径。要解决这个问题,需要将代码中的图片路径修改为你本地图片的正确路径。
以下是修改后的代码,适配适配本地images目录下的图片:
适配本地图片的毛玻璃边框轮播
V1
创建时间:07:57
<!DOCTYPE html>
<html>
<head>
<title>随机图片展示(本地图片版)</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
/* 使用本地背景图或纯色背景 */
background-color: #222; /* 改为深色背景,避免与本地图片冲突 */
}
.image-container {
position: relative;
width: 100%;
height: 100%;
}
/* 左右毛玻璃边框 */
.image-container::before,
.image-container::after {
content: '';
position: absolute;
top: 0;
height: 100%;
width: 15%;
z-index: 5;
pointer-events: none;
}
.image-container::before {
left: 0;
background: linear-gradient(90deg,
rgba(0, 0, 0, 0.3) 0%,
rgba(0, 0, 0, 0) 100%);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.image-container::after {
right: 0;
background: linear-gradient(270deg,
rgba(0, 0, 0, 0.3) 0%,
rgba(0, 0, 0, 0) 100%);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.image-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
transform: scale(1);
object-fit: contain;
opacity: 0.98;
}
.image-layer.transitioning {
transition: opacity 2.5s cubic-bezier(0.25, 0.1, 0.25, 1),
transform 2.5s cubic-bezier(0.25, 0.1, 0.25, 1);
}
.image-layer.active {
opacity: 1;
animation: breathe 10s ease-in-out infinite;
}
@keyframes breathe {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.03);
}
}
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
font-size: 18px;
z-index: 10;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
padding: 10px 20px;
border-radius: 8px;
background-color: rgba(0,0,0,0.2);
}
</style>
</head>
<body>
<div class="image-container">
<div class="loading" id="loading">图片加载中...</div>
<img class="image-layer" id="image1" src="" alt="轮播图片">
<img class="image-layer" id="image2" src="" alt="轮播图片">
</div>
<script>
// 关键修改:使用本地images目录下的图片
// 请将以下列表替换为你实际的图片文件名
var images = [
'images/photo1.jpg', // 替换为你的图片1
'images/photo2.png', // 替换为你的图片2
'images/photo3.jpg', // 替换为你的图片3
'images/photo4.png' // 替换为你的图片4
];
var image1 = document.getElementById('image1');
var image2 = document.getElementById('image2');
var loading = document.getElementById('loading');
var currentLayer = 1;
var imageInterval;
var isTransitioning = false;
// 新增:检查图片是否存在的辅助函数
function checkImageExists(src) {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve(true);
img.onerror = () => resolve(false);
img.src = src;
});
}
function preloadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(src);
img.onerror = () => reject(new Error(`图片加载失败: ${src}(请检查路径是否正确)`));
img.src = src;
});
}
function adjustImageDisplay(imgElement, src) {
return new Promise((resolve) => {
const tempImg = new Image();
tempImg.onload = () => {
const imgRatio = tempImg.width / tempImg.height;
const containerRatio = window.innerWidth / window.innerHeight;
if (imgRatio < 1) {
imgElement.style.objectFit = 'contain';
} else {
imgElement.style.objectFit = 'contain';
}
resolve();
};
tempImg.src = src;
});
}
async function fadeToNewImage(src) {
if (isTransitioning || !src) return;
isTransitioning = true;
const nextLayer = currentLayer === 1 ? 2 : 1;
const currentImg = currentLayer === 1 ? image1 : image2;
const nextImg = nextLayer === 1 ? image1 : image2;
try {
// 先检查图片是否存在
const exists = await checkImageExists(src);
if (!exists) {
throw new Error(`图片不存在: ${src}`);
}
await preloadImage(src);
await adjustImageDisplay(nextImg, src);
nextImg.src = src;
nextImg.className = 'image-layer';
const currentTransform = window.getComputedStyle(currentImg).transform;
let currentScale = 1;
if (currentTransform && currentTransform !== 'none') {
const matrix = currentTransform.match(/matrix.*\((.+)\)/);
if (matrix) {
const values = matrix[1].split(', ');
currentScale = parseFloat(values[0]) || 1;
}
}
currentImg.classList.add('transitioning');
nextImg.classList.add('transitioning');
nextImg.offsetHeight;
requestAnimationFrame(() => {
if (currentImg.classList.contains('active')) {
currentImg.style.transform = `scale(${currentScale})`;
currentImg.style.opacity = '1';
requestAnimationFrame(() => {
currentImg.style.opacity = '0';
currentImg.style.transform = 'scale(0.98)';
});
}
nextImg.style.opacity = '0';
nextImg.style.transform = 'scale(1)';
requestAnimationFrame(() => {
nextImg.style.opacity = '1';
nextImg.style.transform = 'scale(1)';
});
});
setTimeout(() => {
currentImg.className = 'image-layer';
currentImg.style.cssText = '';
nextImg.className = 'image-layer active';
nextImg.style.cssText = '';
currentLayer = nextLayer;
isTransitioning = false;
}, 2500);
} catch (error) {
console.error(error);
loading.textContent = error.message;
isTransitioning = false;
setTimeout(changeImage, 2000);
}
}
async function initFirstImage() {
if (images.length === 0) {
loading.textContent = '暂无图片可展示';
return;
}
const firstSrc = images[Math.floor(Math.random() * images.length)];
try {
await preloadImage(firstSrc);
await adjustImageDisplay(image1, firstSrc);
image1.src = firstSrc;
setTimeout(() => {
image1.className = 'image-layer active';
loading.style.opacity = '0';
setTimeout(() => loading.style.display = 'none', 500);
}, 300);
} catch (error) {
console.error('初始化图片失败:', error);
loading.textContent = error.message;
setTimeout(initFirstImage, 2000);
}
}
function changeImage() {
if (images.length === 0) {
clearInterval(imageInterval);
return;
}
const randomSrc = images[Math.floor(Math.random() * images.length)];
fadeToNewImage(randomSrc);
}
// 初始化本地图片展示
initFirstImage();
imageInterval = setInterval(changeImage, 15000);
// 注释掉远程获取图片的代码,因为我们使用本地图片
/*
setInterval(async () => {
try {
const response = await fetch('/api/images');
if (!response.ok) throw new Error('获取图片列表失败');
const data = await response.json();
images = data.images || [];
if (images.length === 0) {
clearInterval(imageInterval);
image1.className = 'image-layer';
image2.className = 'image-layer';
loading.style.display = 'block';
loading.textContent = '暂无图片可展示';
} else {
if (!imageInterval) {
imageInterval = setInterval(changeImage, 15000);
loading.style.display = 'none';
}
}
} catch (error) {
console.error('刷新图片列表失败:', error);
}
}, 60000);
*/
</script>
</body>
</html>📌关键修改说明
- 图片路径调整:
- 将
images数组中的图片路径修改为本地路径格式:'images/文件名' - 请务必将示例中的
photo1.jpg等替换为你实际的图片文件名
- 将
- 本地适配优化:
- 注释掉了从远程 API 获取图片列表的代码,因为我们使用本地图片
- 背景从在线图片改为深色纯色背景,避免与本地图片风格冲突
- 错误处理增强:
- 新增了
checkImageExists函数,专门检查本地图片是否存在 - 错误提示中会显示具体的图片路径,方便你排查路径问题
- 新增了
- 毛玻璃效果调整:
- 边框颜色从白色系改为深色系(
rgba(0, 0, 0, 0.3)),更适合本地图片展示
- 边框颜色从白色系改为深色系(
📌排查本地图片不显示的常见问题
- 路径是否正确:
- 确保你的 HTML 文件和
images目录在同一级目录 - 例如:
index.html和images文件夹平级,图片放在images里
- 确保你的 HTML 文件和
- 文件名是否正确:
- 注意文件名的大小写(尤其是在 Linux/macOS 系统)
- 扩展名是否正确(是
jpg还是jpeg,png还是gif)
- 文件权限问题:
- 确保图片文件有可读权限
如果仍然无法显示,可以打开浏览器的开发者工具(F12),查看控制台中的错误信息,那里会显示具体的图片加载失败原因。
v4-2025.9.14-不能自动扫描目录,需结合后端(待解决)
问题描述
var images = [
'images/photo1.jpg', // 替换为你的图片1
'images/photo2.png', // 替换为你的图片2
'images/photo3.jpg', // 替换为你的图片3
'images/photo4.png' // 替换为你的图片4
];
这里我不想每次都手动写路径,能不能自动扫描这个images目录下的图片呢?豆包输出
要实现自动扫描images目录下的图片并动态加载,需要结合后端程序来实现目录扫描(因为浏览器的安全限制,JavaScript 无法直接访问本地文件系统)。以下是一个前后端结合的解决方案:
📌实现思路
- 后端(Go)提供一个 API 接口,用于扫描
images目录并返回所有图片路径 - 前端通过这个 API 获取图片列表,而不是手动维护路径数组
📌前端代码修改(HTML/JavaScript)
自动扫描图片目录的轮播
V1
创建时间:07:59
<!DOCTYPE html>
<html>
<head>
<title>随机图片展示(自动扫描版)</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
/* 保持原有样式不变 */
html, body {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
background-color: #222;
}
.image-container {
position: relative;
width: 100%;
height: 100%;
}
.image-container::before,
.image-container::after {
content: '';
position: absolute;
top: 0;
height: 100%;
width: 15%;
z-index: 5;
pointer-events: none;
}
.image-container::before {
left: 0;
background: linear-gradient(90deg,
rgba(0, 0, 0, 0.3) 0%,
rgba(0, 0, 0, 0) 100%);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.image-container::after {
right: 0;
background: linear-gradient(270deg,
rgba(0, 0, 0, 0.3) 0%,
rgba(0, 0, 0, 0) 100%);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.image-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
transform: scale(1);
object-fit: contain;
opacity: 0.98;
}
.image-layer.transitioning {
transition: opacity 2.5s cubic-bezier(0.25, 0.1, 0.25, 1),
transform 2.5s cubic-bezier(0.25, 0.1, 0.25, 1);
}
.image-layer.active {
opacity: 1;
animation: breathe 10s ease-in-out infinite;
}
@keyframes breathe {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.03);
}
}
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
font-size: 18px;
z-index: 10;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
padding: 10px 20px;
border-radius: 8px;
background-color: rgba(0,0,0,0.2);
}
</style>
</head>
<body>
<div class="image-container">
<div class="loading" id="loading">加载图片列表中...</div>
<img class="image-layer" id="image1" src="" alt="轮播图片">
<img class="image-layer" id="image2" src="" alt="轮播图片">
</div>
<script>
var images = []; // 空数组,将从API获取
var image1 = document.getElementById('image1');
var image2 = document.getElementById('image2');
var loading = document.getElementById('loading');
var currentLayer = 1;
var imageInterval;
var isTransitioning = false;
// 从API获取图片列表
async function fetchImageList() {
try {
const response = await fetch('/api/images');
if (!response.ok) {
throw new Error(`获取图片列表失败: ${response.status}`);
}
const data = await response.json();
return data.images || [];
} catch (error) {
console.error('获取图片列表出错:', error);
return [];
}
}
function preloadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(src);
img.onerror = () => reject(new Error(`图片加载失败: ${src}`));
img.src = src;
});
}
function adjustImageDisplay(imgElement, src) {
return new Promise((resolve) => {
const tempImg = new Image();
tempImg.onload = () => {
const imgRatio = tempImg.width / tempImg.height;
const containerRatio = window.innerWidth / window.innerHeight;
if (imgRatio < 1) {
imgElement.style.objectFit = 'contain';
} else {
imgElement.style.objectFit = 'contain';
}
resolve();
};
tempImg.src = src;
});
}
async function fadeToNewImage(src) {
if (isTransitioning || !src) return;
isTransitioning = true;
const nextLayer = currentLayer === 1 ? 2 : 1;
const currentImg = currentLayer === 1 ? image1 : image2;
const nextImg = nextLayer === 1 ? image1 : image2;
try {
await preloadImage(src);
await adjustImageDisplay(nextImg, src);
nextImg.src = src;
nextImg.className = 'image-layer';
const currentTransform = window.getComputedStyle(currentImg).transform;
let currentScale = 1;
if (currentTransform && currentTransform !== 'none') {
const matrix = currentTransform.match(/matrix.*\((.+)\)/);
if (matrix) {
const values = matrix[1].split(', ');
currentScale = parseFloat(values[0]) || 1;
}
}
currentImg.classList.add('transitioning');
nextImg.classList.add('transitioning');
nextImg.offsetHeight;
requestAnimationFrame(() => {
if (currentImg.classList.contains('active')) {
currentImg.style.transform = `scale(${currentScale})`;
currentImg.style.opacity = '1';
requestAnimationFrame(() => {
currentImg.style.opacity = '0';
currentImg.style.transform = 'scale(0.98)';
});
}
nextImg.style.opacity = '0';
nextImg.style.transform = 'scale(1)';
requestAnimationFrame(() => {
nextImg.style.opacity = '1';
nextImg.style.transform = 'scale(1)';
});
});
setTimeout(() => {
currentImg.className = 'image-layer';
currentImg.style.cssText = '';
nextImg.className = 'image-layer active';
nextImg.style.cssText = '';
currentLayer = nextLayer;
isTransitioning = false;
}, 2500);
} catch (error) {
console.error(error);
loading.textContent = error.message;
isTransitioning = false;
setTimeout(changeImage, 2000);
}
}
async function initFirstImage() {
// 先从API获取图片列表
images = await fetchImageList();
if (images.length === 0) {
loading.textContent = 'images目录中未找到图片';
return;
}
const firstSrc = images[Math.floor(Math.random() * images.length)];
try {
await preloadImage(firstSrc);
await adjustImageDisplay(image1, firstSrc);
image1.src = firstSrc;
setTimeout(() => {
image1.className = 'image-layer active';
loading.style.opacity = '0';
setTimeout(() => loading.style.display = 'none', 500);
}, 300);
} catch (error) {
console.error('初始化图片失败:', error);
loading.textContent = error.message;
setTimeout(initFirstImage, 2000);
}
}
function changeImage() {
if (images.length === 0) {
clearInterval(imageInterval);
return;
}
const randomSrc = images[Math.floor(Math.random() * images.length)];
fadeToNewImage(randomSrc);
}
// 初始化
initFirstImage();
// 定时切换
imageInterval = setInterval(changeImage, 15000);
// 每60秒刷新一次图片列表(支持动态添加新图片)
setInterval(async () => {
const newImages = await fetchImageList();
if (newImages.length > 0) {
images = newImages;
// 如果之前没有图片,现在有了就重新开始轮播
if (!imageInterval) {
imageInterval = setInterval(changeImage, 15000);
loading.style.display = 'none';
initFirstImage();
}
} else {
clearInterval(imageInterval);
imageInterval = null;
loading.style.display = 'block';
loading.textContent = 'images目录中未找到图片';
}
}, 60000);
</script>
</body>
</html>📌后端代码(Go)
需要在你的 Go 项目中添加一个 API 接口,用于扫描images目录并返回图片路径列表:
Go图片扫描API
V1
创建时间:07:59
package main
import (
"encoding/json"
"net/http"
"os"
"path/filepath"
"strings"
)
// 图片文件扩展名
var imageExtensions = map[string]bool{
".jpg": true,
".jpeg": true,
".png": true,
".gif": true,
".bmp": true,
".webp": true,
}
// 扫描images目录下的所有图片
func scanImages() ([]string, error) {
var images []string
root := "images" // 图片目录
// 检查目录是否存在
if _, err := os.Stat(root); os.IsNotExist(err) {
return images, nil // 目录不存在返回空列表
}
// 遍历目录
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 只处理文件
if !info.IsDir() {
ext := strings.ToLower(filepath.Ext(path))
// 检查是否为图片文件
if imageExtensions[ext] {
images = append(images, path)
}
}
return nil
})
return images, err
}
// API处理器:返回图片列表
func imagesHandler(w http.ResponseWriter, r *http.Request) {
images, err := scanImages()
if err != nil {
http.Error(w, "扫描图片失败: "+err.Error(), http.StatusInternalServerError)
return
}
// 设置响应头为JSON
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"images": images,
})
}
func main() {
// 静态文件服务:提供HTML和图片访问
http.Handle("/", http.FileServer(http.Dir(".")))
// 图片列表API
http.HandleFunc("/api/images", imagesHandler)
// 启动服务器
http.ListenAndServe(":8080", nil)
}📌实现原理
- 后端(Go):
- 创建
/api/images接口,使用filepath.Walk扫描images目录 - 过滤出图片格式文件(支持 jpg、png、gif 等常见格式)
- 以 JSON 格式返回图片路径列表
- 创建
- 前端(JavaScript):
- 页面加载时通过
fetch('/api/images')获取图片列表 - 每 60 秒自动刷新一次图片列表,支持动态添加新图片
- 无需手动维护
images数组,完全自动获取
- 页面加载时通过
📌使用方法
- 将 HTML 文件放在项目根目录
- 创建
images目录并放入图片 - 运行 Go 程序:
go run main.go - 访问
http://localhost:8080即可看到自动加载的图片轮播
这样,你只需将图片放入images目录,无需修改任何代码,轮播会自动识别并加载新图片。