效果图



参数说明:
参数名 必填 描述 默认值 示例值
text 是 要显示的多段文本,用英文逗号分隔 Hello text=苹果,香蕉,橘子
interval 否 每条文本停留时间(单位:秒) 3 interval=5
dur 否 过渡动画时长(单位:秒) 0.5 dur=0.3
effect 否 动画效果类型 slide effect=fade
noloop 否 添加此参数时禁用循环播放 无(默认循环) 任意值触发(如 &noloop)
fs 否 字体大小(单位:像素) 24 fs=36
color 否 文字颜色(支持HEX/RGB,需URL编码) #334155 color=%23ff0000
bg 否 背景颜色(HEX/RGB或渐变色,需URL编码) #f3f4f6 bg=%23e5e7eb
w 否 SVG画布宽度(单位:像素) 400 w=600
h 否 SVG画布高度(单位:像素) 120 h=200
r 否 背景圆角半径(单位:像素) 8 r=15
shadow 否 添加此参数时启用背景阴影 无(默认无阴影) 任意值触发(如 &shadow)
export default {
async fetch(request) {
const headers = {
'Content-Type': 'image/svg+xml',
'Cache-Control': 'public, max-age=600',
'Access-Control-Allow-Origin': '*'
};
try {
const url = new URL(request.url);
const params = url.searchParams;
// Configuration parameters with default values
const config = {
texts: (params.get('text') || 'Hello').split(','), // Multiple texts separated by commas
interval: parseInt(params.get('interval')) || 3, // Display duration per text (seconds)
loop: params.has('noloop') ? false : true, // Enable continuous loop playback
effect: params.get('effect') || 'slide', // Animation type: fade/slide
duration: parseFloat(params.get('dur')) || 0.2, // Transition duration (seconds)
fontSize: parseInt(params.get('fs')) || 24, // Font size in pixels
color: decodeURIComponent(params.get('color') || '#334155'), // Text color
bgColor: decodeURIComponent(params.get('bg') || '#f3f4f6'), // Background color
width: parseInt(params.get('w')) || 400, // SVG canvas width
height: parseInt(params.get('h')) || 120, // SVG canvas height
radius: parseInt(params.get('r')) || 8, // Border radius value
shadow: params.has('shadow') ? true : false // Enable drop shadow effect
};
// Calculate total animation cycle duration
const totalDuration = config.texts.length * config.interval;
// Generate animated text elements with timing controls
const animations = config.texts.map((text, index) => {
const start = index * config.interval;
const fadeInEnd = start + config.duration;
const fadeOutStart = (index + 1) * config.interval - config.duration;
const end = (index + 1) * config.interval;
let animationElement;
if (config.effect === 'slide') {
animationElement = `
<text x="-100%" y="50%"
dominant-baseline="middle"
text-anchor="middle"
font-family="system-ui, sans-serif"
font-size="${config.fontSize}px"
fill="${config.color}">
${text}
<animate attributeName="x"
values="-100%;50%;50%;200%"
keyTimes="0;${fadeInEnd/totalDuration};${fadeOutStart/totalDuration};1"
dur="${totalDuration}s"
repeatCount="${config.loop ? 'indefinite' : '1'}"
calcMode="spline"
keySplines="0.4 0 0.2 1;0 0 1 1;0.4 0 0.2 1"/>
</text>
`;
} else {
animationElement = `
<text x="50%" y="50%"
dominant-baseline="middle"
text-anchor="middle"
font-family="system-ui, sans-serif"
font-size="${config.fontSize}px"
fill="${config.color}"
opacity="0">
${text}
<animate attributeName="opacity"
values="0;1;1;0"
keyTimes="0;${fadeInEnd/totalDuration};${(fadeOutStart + 0.1)/totalDuration};1"
dur="${totalDuration}s"
repeatCount="${config.loop ? 'indefinite' : '1'}"
calcMode="spline"
keySplines="0.5 0 0.5 1;0 0 1 1;0.5 0 0.5 1"/>
</text>
`;
}
return animationElement;
}).join('');
// Construct complete SVG structure
const svgContent = `
<svg xmlns="http://www.w3.org/2000/svg"
width="${config.width}"
height="${config.height}"
viewBox="0 0 ${config.width} ${config.height}">
<defs>
${config.shadow ? `
<filter id="shadow">
<feDropShadow dx="2" dy="2" stdDeviation="2" flood-color="rgba(0,0,0,0.1)"/>
</filter>` : ''}
</defs>
<rect x="0" y="0"
width="100%"
height="100%"
rx="${config.radius}"
fill="${config.bgColor}"
${config.shadow ? 'filter="url(#shadow)"' : ''}/>
${animations}
</svg>
`;
return new Response(svgContent, { headers });
} catch (error) {
return new Response(`<svg xmlns="http://www.w3.org/2000/svg">
<text x="50%" y="50%" text-anchor="middle" fill="red">Error: ${error.message}</text>
</svg>`, { headers });
}
}
};