使用手册 By:挽歌
项目简介
本项目旨在通过网页表单和Canvas绘图技术,生成带有文本、图片和随机时间的图像。用户可以上传图片、头像文件夹、昵称文件、SVG文件夹和默认文件夹,设置生成数量和时间范围,并生成指定数量的图像。有需要找泡泡,项目完全开源!
作者:挽歌
小绿泡泡:LogoMY1314
文件结构
index.html
: 主网页文件,包含表单和Canvas元素。styles.css
: 样式文件,定义网页的外观。script.js
: JavaScript文件,包含主要逻辑和绘图代码。
部件说明
1. 表单字段
1.1 文本内容
- ID:
content
- 类型:
textarea
- 说明: 用于输入要显示在图像上的文本内容。支持换行,满18个字符自动换行。
1.2 图片文件
- ID:
image
- 类型:
file
- 说明: 上传要显示在图像中的主要图片。
1.3 头像文件夹
- ID:
avatarFolder
- 类型:
file
(multiple) - 说明: 上传包含多个头像图片的文件夹。生成图像时会随机选择头像。
1.4 昵称文件
- ID:
nicknames
- 类型:
file
- 说明: 上传包含昵称的文本文件,每行一个昵称。
1.5 SVG文件夹
- ID:
svgFolder
- 类型:
file
(multiple) - 说明: 上传包含多个SVG文件的文件夹,生成图像时会随机选择一个SVG文件作为手机通知栏。
1.6 默认文件夹
- ID:
defaultFolder
- 类型:
file
(multiple) - 说明: 上传包含默认图片文件的文件夹。需要包含以下文件:
default_avatar
: 默认头像图片default_details
: 详情图片default_comment
: 评论区图片
1.7 起始时间
- ID:
startTime
- 类型:
time
- 说明: 设置随机时间的起始时间。
1.8 结束时间
- ID:
endTime
- 类型:
time
- 说明: 设置随机时间的结束时间。
1.9 生成数量
- ID:
generateCount
- 类型:
number
- 说明: 设置要生成的图像数量。
1.10 预览按钮
- ID:
preview
- 类型:
button
- 说明: 点击预览生成的图像。
1.11 生成按钮
- ID:
generate
- 类型:
button
- 说明: 点击生成指定数量的图像,并保存到本地。
2. Canvas
- ID:
canvas
- 类型:
canvas
- 说明: 用于绘制图像的画布。
使用步骤
1. 打开网页
在浏览器中打开 index.html
文件。
2. 填写表单
按以下步骤填写表单:
- 文本内容: 在
content
文本区域输入要显示的文本内容。 - 图片文件: 点击
image
文件选择器,上传要显示的主要图片。 - 头像文件夹: 点击
avatarFolder
文件选择器,选择包含多个头像图片的文件夹。 - 昵称文件: 点击
nicknames
文件选择器,上传包含昵称的文本文件。 - SVG文件夹: 点击
svgFolder
文件选择器,选择包含多个SVG文件的文件夹。 - 默认文件夹: 点击
defaultFolder
文件选择器,选择包含默认图片文件的文件夹。 - 起始时间: 在
startTime
输入框中设置随机时间的起始时间。 - 结束时间: 在
endTime
输入框中设置随机时间的结束时间。 - 生成数量: 在
generateCount
输入框中设置要生成的图像数量。
3. 预览图像
点击 preview
按钮,预览生成的图像。
4. 生成图像
点击 generate
按钮,生成指定数量的图像,并保存到本地。生成完成后,会弹出提示框,显示生成的数量,并附上问候语。
示例代码
HTML
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图像生成器</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>图像生成器</h1>
<form>
<label for="content">文本内容:</label>
<textarea id="content" rows="4" cols="50"></textarea><br><br>
<label for="image">图片文件:</label>
<input type="file" id="image"><br><br>
<label for="avatarFolder">头像文件夹:</label>
<input type="file" id="avatarFolder" multiple><br><br>
<label for="nicknames">昵称文件:</label>
<input type="file" id="nicknames"><br><br>
<label for="svgFolder">SVG文件夹:</label>
<input type="file" id="svgFolder" multiple><br><br>
<label for="defaultFolder">默认文件夹:</label>
<input type="file" id="defaultFolder" multiple><br><br>
<label for="startTime">起始时间:</label>
<input type="time" id="startTime"><br><br>
<label for="endTime">结束时间:</label>
<input type="time" id="endTime"><br><br>
<label for="generateCount">生成数量:</label>
<input type="number" id="generateCount" value="1"><br><br>
<button type="button" id="preview">预览</button>
<button type="button" id="generate">生成</button>
</form>
<canvas id="canvas" style="display:none;"></canvas>
<script src="script.js"></script>
</body>
</html>
CSS
body {
font-family: Arial, sans-serif;
margin: 20px;
}
h1 {
text-align: center;
}
form {
margin-bottom: 20px;
}
label {
display: block;
margin-top: 10px;
}
textarea {
width: 100%;
}
input[type="file"],
input[type="time"],
input[type="number"] {
width: 100%;
}
button {
margin-top: 10px;
padding: 10px 20px;
font-size: 16px;
}
JavaScript
document.getElementById('preview').addEventListener('click', async function() {
const content = document.getElementById('content').value || '';
const imageFile = document.getElementById('image').files[0];
const avatarFiles = document.getElementById('avatarFolder').files;
const nicknamesFile = document.getElementById('nicknames').files[0];
const svgFiles = document.getElementById('svgFolder').files;
const defaultFiles = document.getElementById('defaultFolder').files;
const startTime = document.getElementById('startTime').value;
const endTime = document.getElementById('endTime').value;
if (!imageFile || avatarFiles.length === 0 || !nicknamesFile || svgFiles.length === 0 || defaultFiles.length === 0 || !startTime || !endTime) {
alert('请填写所有字段并选择文件夹和文件!');
return;
}
const randomTime = getRandomTime(startTime, endTime);
console.log('随机时间:', randomTime);
const nicknames = await readFileAsText(nicknamesFile);
const nicknameList = nicknames.split('\n').filter(name => name.trim() !== '');
const imageSrc = await readFileAsDataURL(imageFile);
const defaultAvatarSrc = await getFileFromFolder(defaultFiles, 'default_avatar');
const detailsSrc = await getFileFromFolder(defaultFiles, 'default_details');
const commentsSectionSrc = await getFileFromFolder(defaultFiles, 'default_comment');
const notificationBarSrc = await readFileAsDataURL(svgFiles[0]);
await drawCanvas(content, imageSrc, defaultAvatarSrc, nicknameList[0], notificationBarSrc, detailsSrc, commentsSectionSrc, false, randomTime);
});
document.getElementById('generate').addEventListener('click', async function() {
const content = document.getElementById('content').value || '';
const imageFile = document.getElementById('image').files[0];
const avatarFiles = document.getElementById('avatarFolder').files;
const nicknamesFile = document.getElementById('nicknames').files[0];
const svgFiles = document.getElementById('svgFolder').files;
const defaultFiles = document.getElementById('defaultFolder').files;
const startTime = document.getElementById('startTime').value;
const endTime = document.getElementById('endTime').value;
if (!imageFile || avatarFiles.length === 0 || !nicknamesFile || svgFiles.length === 0 || defaultFiles.length === 0 || !startTime || !endTime) {
alert('请填写所有字段并选择文件夹和文件!');
return;
}
const generateCount = document.getElementById('generateCount').value;
const nicknames = await readFileAsText(nicknamesFile);
const nicknameList = nicknames.split('\n').filter(name => name.trim() !== '');
const imageSrc = await readFileAsDataURL(imageFile);
const defaultAvatarSrc = await getFileFromFolder(defaultFiles, 'default_avatar');
const detailsSrc = await getFileFromFolder(defaultFiles, 'default_details');
const commentsSectionSrc = await getFileFromFolder(defaultFiles, 'default_comment');
for (let i = 0; i < generateCount; i++) {
const avatarSrc = await readFileAsDataURL(avatarFiles[i % avatarFiles.length]);
const notificationBarSrc = await readFileAsDataURL(svgFiles[i % svgFiles.length]);
const randomTime = getRandomTime(startTime, endTime);
await drawCanvas(content, imageSrc, avatarSrc, nicknameList[i % nicknameList.length], notificationBarSrc, detailsSrc, commentsSectionSrc, true, randomTime);
}
// 完成提示
alert(`已成功生成 ${generateCount} 张图像。感谢您的使用,祝您愉快!`);
});
async function drawCanvas(content, imageSrc, avatarSrc, nickname, notificationBarSrc, detailsSrc, commentsSectionSrc, save = false, randomTime) {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 1280;
canvas.height = 2774;
// Fill background with white color
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw notification bar
const notificationBarImg = await loadImage(notificationBarSrc);
ctx.drawImage(notificationBarImg, 0, 0, 1280, 150);
// Draw details section
const detailsImg = await loadImage(detailsSrc);
ctx.drawImage(detailsImg, 0, 150, 1280, 150);
// Draw avatar
const avatarImg = await loadImage(avatarSrc);
ctx.drawImage(avatarImg, 45, 320, 125, 125);
// Draw nickname
ctx.font = '50px 黑体';
ctx.fillStyle = '#556c98';
ctx.fillText(nickname, 200, 360);
let contentY = 420;
if (content) {
// Draw content text with specified letter spacing and line height
ctx.font = '50px Arial';
ctx.fillStyle = 'black';
ctx.letterSpacing = '3px'; // Set letter spacing
const lines = wrapText(ctx, content, 200, contentY, 1045, 55); // Line height adjusted to 55px for 5px line spacing
contentY += lines.length * 55; // 55 is line height including line spacing
}
// Draw content image
const contentImg = await loadImage(imageSrc);
const imgWidth = 1045;
const imgHeight = contentImg.height * (imgWidth / contentImg.width);
ctx.drawImage(contentImg, 200, contentY - 15, imgWidth, imgHeight); // Move image up by 5.5px
// Draw white rectangle for time
ctx.fillStyle = '#ffffff';
ctx.fillRect(200, contentY + imgHeight - 50, 120, 50); // Move up by 15px
// Display random time on white rectangle, bold
ctx.font = '45px 宋体';
ctx.fillStyle = '#aeb0af';
ctx.fillText(randomTime, 200, contentY + imgHeight - 15 - 18); // Move up by 15px
// Draw comments section
const commentsSectionImg = await loadImage(commentsSectionSrc);
ctx.drawImage(commentsSectionImg, 0, 2515, 1280, 259);
if (save) {
// Save the canvas as an image
const link = document.createElement('a');
link.download = `screenshot_${nickname}.png`;
link.href = canvas.toDataURL();
link.click();
}
}
function wrapText(ctx, text, x, y, maxWidth, lineHeight) {
const lines = [];
const paragraphs = text.split('\n');
paragraphs.forEach(paragraph => {
let line = '';
for (let i = 0; i < paragraph.length; i++) {
line += paragraph[i];
if (line.length >= 18 || i === paragraph.length - 1) {
lines.push(line);
line = '';
}
}
});
lines.forEach((line, index) => {
ctx.fillText(line, x, y + (index * lineHeight));
});
return lines;
}
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}
function readFileAsText(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsText(file);
});
}
function readFileAsDataURL(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
function getFileFromFolder(files, filename) {
for (let file of files) {
if (file.name.includes(filename)) {
return readFileAsDataURL(file);
}
}
throw new Error(`文件夹中未找到文件: ${filename}`);
}
function getRandomTime(startTime, endTime) {
const start = new Date();
const end = new Date();
const [startHour, startMinute] = startTime.split(':').map(Number);
const [endHour, endMinute] = endTime.split(':').map(Number);
start.setHours(startHour);
start.setMinutes(startMinute);
start.setSeconds(0);
end.setHours(endHour);
end.setMinutes(endMinute);
end.setSeconds(0);
const randomDate = new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()));
const hours = String(randomDate.getHours()).padStart(2, '0');
const minutes = String(randomDate.getMinutes()).padStart(2, '0');
return `${hours}:${minutes}`;
}
请参考前面提供的 script.js
代码。
联系作者
如有任何问题或建议,请联系作者:
- 作者: 挽歌
感谢您的使用,祝您愉快!