当前位置: 首页 > news >正文

一周时间,开发了一款封面图生成工具

65d7e457b690c9d803d4d34d50428547.png

介绍

这是一款封面图的制作工具,根据简单的配置即可生成一张好看的封面图,目前已有七款主题可以选择。做这个工具的初衷来自平时写文章,都为封面图发愁,去图片 网站上搜索很难找到满意的,而且当你要的图如果要搭配上文章的标题,使用 Photoshop 等软件操作成本太大。为此突然来了灵感,何不自己开发一个在线的工具直接生成。

开发前期构思

一款工具型的软件,界面一定要简洁,操作方面。所以在布局上没有必要占满整个页面,宽屏上限定宽度然后相对居中。

内容上软件整体上会分成三块:

  • 预览区域

  • 内容配置区域

  • 样式配置区域

这样一来布局上可以采取列布局或者行布局。我能想到的有:

db73abb306467a44eff5e7b9c143f573.png

由于根据个人喜好最终定下来第二种样式的布局。

代码实现

根据布局,我定义了三个函数组件来实现对应的“预览区”、“内容配置区”和“样式配置区”和一个主页面渲染函数。

// 页面主函数
export function Main(props) {// ...
}
// 内容配置函数
export function ContentForm(props) {// ...
}
// 样式配置函数
export function ConfigForm(props) {// ...
}
// 封面图预览函数
export function CoverImage(props) {// ...
}

这里 UI 组件是引用 Material UI[1],也是本站引用的唯一外部 UI 框架。

页面主函数

主函数中定义了全局共享的配置变量 config 和改变状态的函数 handleConfigChange。它们两会当成参数传入到其它组件中使用。

export function Main({ normal }) {const coverRef = useRef();const [config, setConfig] = useState({font: 'serif',bgColor: '#949ee5',gradientBgColor: '',icon: 'react',ratio: 0.5,width: 800,title: '欢迎来到太空编程站点',author: '编程范儿',theme: 'basic',bgImg: 'https://spacexcode.oss-cn-hangzhou.aliyuncs.com/1704985651753-e2a2eb6d-71c6-4293-8d8c-49203c7410bb.jpeg'});const handleConfigChange = (val, key) => {setConfig((prev) => ({ ...prev, [key]: val }));};const downloadImage = (scale, format) => {// todo};const handleCopyImg = (cb) => {//todo};return (<Box sx={{ padding: '40px 0' }}><Grid container spacing={3}><Grid item xs={12} md={ normal ? 8 : 12 }><Box className={styles.card} sx={{ padding: '20px 10px', overflowX: 'auto' }}>{/* 生成图显示 */}<div ref={coverRef} className={styles.preview} style={{ width: config.width + 'px'}}><CoverImage config={config} /></div></Box><Box className={styles.card} sx={{ padding: '10px 20px 40px', marginTop: '24px' }}><ContentForm config={config} handleConfigChange={handleConfigChange} /></Box></Grid>{/* 配置 */}<Grid item xs={12} md={normal ? 4 : 6}><Box className={styles.card} sx={{ padding: '10px 20px 40px' }}><ConfigForm config={config} handleConfigChange={handleConfigChange} downloadImage={downloadImage} handleCopyImg={handleCopyImg} /></Box></Grid></Grid></Box>)
}

因为页面主函数主要是集成其它三个组件,没有什么逻辑,我们来一一讲讲“内容配置函数”、“样式配置函数”和“封面图预览函数”这三个函数的实现。

内容配置函数

封面图中的内容配置就三项:标题作者图标

标题和作者是两个简单的文本输入框,图标数据是我本地写了一个列表,图标本身是一段 SVG 代码。使用的 React 函数组件返回。

export function ContentForm ({ config, handleConfigChange }) {return (<><Box className={styles.setItem}><Typography variant="h5">标题</Typography><TextFieldvalue={config.title}onChange={e => handleConfigChange(e.target.value, 'title')}placeholder='标题'size='small'multilinerows={3}fullWidth/></Box><Box className={styles.setItem}><Typography variant="h5">作者</Typography><TextFieldvalue={config.author}onChange={e => handleConfigChange(e.target.value, 'author')}placeholder='作者'size='small'fullWidth/></Box><Box className={styles.setItem}><Typography variant="h5">图标</Typography><Box sx={{ display: 'flex', gap: '10px' }}><Selectvalue={config.icon}onChange={val => handleConfigChange(val.target.value, 'icon')}label=''size='small'fullWidth>{devicons.map(el => (<MenuItem value={el.value} key={el.value}><div className={styles.selectIconRow}><span>{el.label}</span>{selectDevicon(el.value)}</div></MenuItem>))}</Select><Button component="label" size='small' variant="contained" sx={{ width: '120px' }} startIcon={<AddPhotoAlternateIcon />}>上传<VisuallyHiddenInput type="file" onChange={(e) => handleConfigChange(URL.createObjectURL(e.target.files[0]), 'customIcon')} /></Button></Box></Box></>)
}

说明下:这里我只贴主要代码。

样式配置函数

样式配置主要是对封面图的 Layout、上面文字的字体、背景色和图片的长宽进行设置。同时这个区域还包含两个操作按钮:图片的复制和导出。

主题

这里我定义了七款主题,分别对它们进行命名“basic”、“background”、“stylish”、“outline”、“modern”、“preview”和“mobile”。后面会根据 命名对主题进行调用。

在配置里,我们对不同的主题设计了 Layout 模型,放在选项中进行选择,另外还分别对它们建立了真实渲染的文件。分别放在 themes 和 themeSkeleton 两个目录下。

我们这里就以 basic 主题进行讲解,其它类似。

import { Skeleton } from '@mui/material';export default const BasicTheme = () => {return (<div className={styles.basicTheme}><Skeleton animation={false} variant="rectangular" sx={{ padding: '8px' }} width={116} height={68}><div className={styles.content}><Skeleton animation={false} variant="text" width={'100%'} height={10} /><Skeleton animation={false} variant="text" width={'70%'} height={10} /><div className={styles.bt}><Skeleton animation={false} variant="rounded" width={10} height={10} /><Skeleton animation={false} variant="text" width={'20%'} height={8} /></div></div></Skeleton></div>);
}

每个 UI 框架都有 Skeleton 骨架屏组件,我们可以直接使用它来生成我们的主题模型。很轻松就实现了布局。

而主题的渲染组件则要通过读取配置来做实现样式的定制。

export default const BasicTheme = ({ config }) => {const { title, bgColor, gradientBgColor, author, icon, font, customIcon, width, ratio } = config;const height = width * ratio + 'px';return (<div className={styles.basicTheme}><div className={styles.main} style={{ backgroundColor: bgColor, backgroundImage: gradientBgColor, height: height }}><div className={clsx(styles.content, styles['font-' + font])}><div style={{ padding: '0 3rem' }}><h1>{title}</h1></div><div className={styles.bt}>{customIcon ?<div className={styles.customIcon}><img src={customIcon} alt="img" /></div>:<div className={styles.devicon}>{selectDevicon(icon)}</div>}<h2 className={styles.author}>{author}</h2></div></div></div></div>);
}

配置中的主题名和实际的主题组件函数做了映射。

const selectTheme = (theme) => {switch (theme) {case 'basic':return <BasicTheme config={config} />;case 'modern':return <ModernTheme config={config} />;case 'outline':return <OutlineTheme config={config} />;case 'background':return <BackgroundTheme config={config} />;case 'preview':return <PreviewTheme config={config} />;case 'stylish':return <StylishTheme config={config} />;case 'mobile':return <MobileTheme config={config} />;default:return <BasicTheme config={config} />;}
};
字体

字体选项中有每个字体的命名,它们被存在配置变量中,在主题渲染函数中会被用在类名中。然后对相应的类名设置对应的 font-family

背景色

背景色有两种类型:纯色和渐变色,分别通过 CSS 的 background-color 和 background-image 属性进行设置。

渐变色我们预定义了八种:

const bgColorOptions = ['linear-gradient(310deg,rgb(214,233,255),rgb(214,229,255),rgb(209,214,255),rgb(221,209,255),rgb(243,209,255),rgb(255,204,245),rgb(255,204,223),rgb(255,200,199),rgb(255,216,199),rgb(255,221,199))','linear-gradient(160deg,rgb(204,251,252),rgb(197,234,254),rgb(189,211,255))','linear-gradient(150deg,rgb(255,242,158),rgb(255,239,153),rgb(255,231,140),rgb(255,217,121),rgb(255,197,98),rgb(255,171,75),rgb(255,143,52),rgb(255,115,33),rgb(255,95,20),rgb(255,87,15))','linear-gradient(345deg,rgb(211,89,255),rgb(228,99,255),rgb(255,123,247),rgb(255,154,218),rgb(255,185,208),rgb(255,209,214),rgb(255,219,219))','linear-gradient(150deg,rgb(0,224,245),rgb(31,158,255),rgb(51,85,255))','linear-gradient(330deg,rgb(255,25,125),rgb(45,13,255),rgb(0,255,179))','linear-gradient(150deg,rgb(0,176,158),rgb(19,77,93),rgb(16,23,31))','linear-gradient(150deg,rgb(95,108,138),rgb(48,59,94),rgb(14,18,38))'
]

纯色的选择放了一个取色器,另外后面还放了一个随机生成颜色的按钮,这里也是人为定了一些颜色,然后从中随机选取。

长宽设置

长度通过 Slider 滑块组件进行设置,为了保证生成的图片大小在合理的范围内,这里设置了最大和最小边界值,区间范围在 [600, 820] 之间。

宽度的实现是通过设置长宽比来实现的。

1:23:54:75:8 这几个比例都能保证图片有较好的效果。

复制和下载

图片生成好之后,我预想了会有两个动作,一个是下载保存到本地,另一个是为了快捷使用,如果是在聊天工具,类似微信、QQ或者钉钉的聊天框中可直接 粘帖复制好的图片。另外富文本编辑器也支持。

这里我们首先要用到核心组件 html2canvas 来帮我们实现从页面 html 元素转为 canvas 对象,进而实现图片的保存和复制。

const handleCopyImg = (cb) => {if (!coverRef.current) return;html2canvas(coverRef.current, {useCORS: true,scale: 1,backgroundColor: 'transparent'}).then((canvas) => {canvas.toBlob(async blob => {console.log(blob);const data = [new ClipboardItem({[blob.type]: blob,}),];await navigator.clipboard.write(data).then(() => {console.log("复制成功!");cb && cb();},() => {console.error("失败.");});});})
};

图片保存的时候会弹出类型和大小选择的选项,支持 png 和 jpg 格式的导出,另外为了在 retina 屏幕上适配,也提供了 2X 图的导出。

const downloadImage = (scale, format) => {if (!coverRef.current) return;html2canvas(coverRef.current, {useCORS: true,scale: scale,backgroundColor: 'transparent'}).then((canvas) => {let newImg = new Image()const date = new Date()newImg.src = canvas.toDataURL('image/' + format) // 'image/png'const a = document.createElement("a");a.style.display = "none";a.href = newImg.src;a.download = `spacexcode-cover-${date.getMinutes()}${date.getSeconds()}.${format}`;a.rel = "noopener noreferrer";document.body.append(a);a.click();setTimeout(() => {a.remove();}, 1000);})
};

为了做一款好用的工具,还是尽量多想想,包含一些特殊的使用场景。

工具地址:https://spacexcode.com/coverview

参考资料

[1]

Material UI: https://mui.com/

相关文章:

一周时间,开发了一款封面图生成工具

介绍 这是一款封面图的制作工具&#xff0c;根据简单的配置即可生成一张好看的封面图&#xff0c;目前已有七款主题可以选择。做这个工具的初衷来自平时写文章&#xff0c;都为封面图发愁&#xff0c;去图片 网站上搜索很难找到满意的&#xff0c;而且当你要的图如果要搭配上文…...

【.NET Core】深入理解异步编程模型(APM)

【.NET Core】深入理解异步编程模型&#xff08;APM&#xff09; 文章目录 【.NET Core】深入理解异步编程模型&#xff08;APM&#xff09;一、APM概述二、IAsyncResult接口2.1 BeginInvoke2.2 EndInvoke2.3 IAsyncResult属性2.4 IAsyncResult异步演示 三、通过结束异步操作来…...

pyqtgraph绘图类

pyqtgraph绘图类 pyqtgraph绘图有四种方法: 方法描述pyqtgraph.plot()创建一个新的QWindow用来绘制数据PlotWidget.plot()在已存在的QWidget上绘制数据PlotItem.plot()在已存在的QWidget上绘制数据GraphicsLayout.addPlot()在网格布局中添加一个绘图 上面四个方法都接收同样…...

C#6-10新增的内容

目录 异常筛选器 属性语法 表达式主体定义 Null 条件运算符 ?. 和 ?[] 使用 $ 的字符串内插 nameof 表达式 元组类型 模糊匹配 本地函数 Expression-bodied 成员 Reference 变量 ?、??和??= .. 模式匹配功能(C# 9) Record init c#8.NET Framework 4.8…...

【立创EDA-PCB设计基础】3.网络表概念解读+板框绘制

前言&#xff1a;本文对网络表概念解读板框绘制&#xff08;确定PCB板子轮廓&#xff09; 网络表概念解读 在本专栏的上一篇文章【嘉立创EDA-PCB设计指南】2&#xff0c;将设计的原理图转为了PCB&#xff0c;在PCB界面下出现了所有的封装&#xff0c;以及所有的飞线属性&…...

在Python环境中运行R语言的配环境实用教程

前情提要 在做一些生物信息与医学统计的工作&#xff0c;本来偷懒希望只靠python完成的&#xff0c;结果还是需要用R语言&#xff0c;倒腾了一会儿&#xff0c;调成功了&#xff0c;就记录一下这个过程。 我的环境&#xff1a; win10, pycharm, R-4.3.2 首先&#xff0c;我们…...

2023年总结我所经历的技术大变革

&#x1f4e2;欢迎点赞 &#xff1a;&#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff0c;赐人玫瑰&#xff0c;手留余香&#xff01;&#x1f4e2;本文作者&#xff1a;由webmote 原创&#x1f4e2;作者格言&#xff1a;新的征程&#xff0c;我们面对的不仅…...

基于YOLOv7算法的高精度实时车载摄像头下车辆检测系统(PyTorch+Pyside6+YOLOv7)

摘要&#xff1a;基于YOLOv7算法的高精度实时车载摄像头下车辆检测系统可用于日常生活中检测与定位车辆&#xff0c;此系统可完成对输入图片、视频、文件夹以及摄像头方式的目标检测与识别&#xff0c;同时本系统还支持检测结果可视化与导出。本系统采用YOLOv7目标检测算法来训…...

深度学习(3)--递归神经网络(RNN)和词向量模型Word2Vec

目录 一.递归神经网络基础概念 二.自然语言处理-词向量模型Word2Vec 2.1.词向量模型 2.2.常用模型对比 2.3.负采样方案 2.4.词向量训练过程 一.递归神经网络基础概念 递归神经网络(Recursive Neural Network, RNN)可以解决有时间序列的问题&#xff0c;处理诸如树、图这样…...

【江科大】STM32:中断系统(理论)

文章目录 中断系统为什么要使用中断中断优先级中断嵌套STM32的中断系统如何管理这些中断NVIC的结构![请添加图片描述](https://img-blog.csdnimg.cn/c77b038fd63a4ddfbcd3b86f6dfe596b.png) 优先级窗口看门狗&#xff08;WWDG&#xff09;&#xff1a;外部中断模块的特性&#…...

JAVA 学习 面试(六)数据类型与方法

数据类型 基本数据类型 为什么float3.4报错 3.4 默认是浮点double类型的&#xff0c;如果赋值给float是向下转型&#xff0c;会出现精度缺失&#xff0c;&#xff0c;需要强制转换 Switch支持的数据类型&#xff1f; byte、short、int、char 、 enum 、 String 基本类型与包…...

Java 一个数组集合List<People> 赋值给另一个数组集合List<NewPeople> ,两个数组集合属性部分一致。

Java 一个数组集合List 赋值给另一个数组集合List &#xff0c;两个数组集合属性部分一致。 下面是一个Demo, 具体要根据自己的业务调整。 import java.util.ArrayList; import java.util.List;class People {private String name;private int age;private String address;publ…...

基于神经网络的电力系统的负荷预测

一、背景介绍&#xff1a; 电力系统负荷预测是生产部门的重要工作之一&#xff0c;通过准确的负荷预测&#xff0c;可以经济合理地安排机组的启停、减少旋转备用容量、合理安排检修计划、降低发电成本和提高经济效益。负荷预测按预测的时间可以分为长期、中期和短期负荷预测。…...

OpenCV第 1 课 计算机视觉和 OpenCV 介绍

文章目录 第 1 课 计算机视觉和 OpenCV 介绍1.机器是如何“看”的2.机器视觉技术的常见应用3.图像识别介绍4. 图像识别技术的常见应用5.OpenCV 介绍6.图像在计算机中的存储形式 第 1 课 计算机视觉和 OpenCV 介绍 1.机器是如何“看”的 我们人类可以通过眼睛看到五颜六色的世界…...

C++面试:stl的栈和队列介绍

目录 栈 栈&#xff08;stack&#xff09;的声明&#xff1a; push()&#xff1a; 将元素推入栈顶 pop()&#xff1a; 弹出栈顶元素 top()&#xff1a; 访问栈顶元素&#xff0c;但不弹出 empty()&#xff1a; 检查栈是否为空 size()&#xff1a; 返回栈中元素的数量 …...

从0开始学习C++ 第十二课:指针强化

第十二课&#xff1a;指针强化 学习目标&#xff1a; 理解常量指针与指针常量的区别。学习如何使用函数指针。掌握指针与数组的高级使用技巧。 学习内容&#xff1a; 常量指针与指针常量 概念&#xff1a; 常量指针是一个指向常量的指针&#xff0c;这意味着不能通过这个指针…...

mongodb和python交互

1. mongdb和python交互的模块 pymongo 提供了mongdb和python交互的所有方法 安装方式: pip install pymongo 2. 使用pymongo 2.1 导入pymongo并选择要操作的集合 数据库和集合能够自动创建 2.1.1 无需权限认证的方式创建连接对象以及集合操作对象 from pymongo import Mong…...

力扣279. 完全平方数

动态规划 思路&#xff1a; 假设 dp[i] 为最少组成数 i 的平方数个数&#xff1b;则其上一个状态为 dp[i - j^2] 1&#xff0c;1 为 j^2&#xff1a; 即 i 的最少完全平方数 i - j^2 的最少完全平方数 1&#xff0c;其中 j^2 < i 为最接近 i 的平方数&#xff1b;初始值…...

【C++】list容器功能模拟实现

介绍 上一次介绍了list队容器的迭代器模拟&#xff0c;这次模拟实现list的简单功能&#xff0c;尤其要注意构造函数、析构函数、以及赋值运算符重载的实现。 list容器需要接纳所有类型的数据&#xff0c;因此&#xff0c;结构设置与迭代器设置同理&#xff0c;需要引入结点&…...

linux 安装ffmpeg

一、下载 ffmpeg-4.3.1 下载地址&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1xbkpHDfIWSCbHFGJJHSQcA 提取码&#xff1a;3eil 二、上传到服务器root目录下 三、给ffmpeg-4.3.1 读写权限 chmod -R 777 /root/ffmpeg-4.3.1 四、创建软连接 1.进入/bin 目录 2.…...

嵌入式AI模型量化实战:用int8给ResNet减重80%还不掉精度

嵌入式AI模型量化实战&#xff1a;用int8给ResNet减重80%还不掉精度 在边缘计算设备上部署神经网络时&#xff0c;工程师们常常面临一个两难选择&#xff1a;要么接受模型体积过大导致的内存溢出&#xff0c;要么忍受量化带来的精度暴跌。去年我们在智能摄像头项目中就遇到了这…...

J-Link驱动签名被拦?手把手教你用WHQL签名驱动搞定Windows 11安全策略

J-Link驱动签名被拦&#xff1f;手把手教你用WHQL签名驱动搞定Windows 11安全策略 最近在帮团队调试一批新的STM32H7开发板时&#xff0c;遇到了一个令人头疼的问题&#xff1a;明明上周还能正常使用的J-Link调试器&#xff0c;在新的Windows 11企业版电脑上突然无法识别了。设…...

UniHacker终极指南:免费解锁Unity全平台专业功能的完整方案

UniHacker终极指南&#xff1a;免费解锁Unity全平台专业功能的完整方案 【免费下载链接】UniHacker 为Windows、MacOS、Linux和Docker修补所有版本的Unity3D和UnityHub 项目地址: https://gitcode.com/GitHub_Trending/un/UniHacker 作为一名Unity开发者&#xff0c;你是…...

AI开发不再卡顿:RTX4090D 24G镜像解决环境冲突全攻略

AI开发不再卡顿&#xff1a;RTX4090D 24G镜像解决环境冲突全攻略 1. 为什么选择RTX4090D 24G深度学习镜像&#xff1f; 深度学习开发者最头疼的问题莫过于环境配置。不同框架版本、CUDA版本、依赖库之间的冲突常常让人望而却步。传统环境搭建方式需要&#xff1a; 手动安装C…...

Qwen2.5-VL半监督学习效果展示:有限标注下的性能提升

Qwen2.5-VL半监督学习效果展示&#xff1a;有限标注下的性能提升 1. 引言 在AI视觉领域&#xff0c;标注数据一直是制约模型性能的关键因素。传统监督学习需要大量人工标注&#xff0c;成本高、周期长&#xff0c;让很多企业和研究者望而却步。但今天&#xff0c;随着半监督学…...

OpenClaw技能分享:GLM-4.7-Flash驱动的邮件自动处理系统

OpenClaw技能分享&#xff1a;GLM-4.7-Flash驱动的邮件自动处理系统 1. 为什么需要自动化邮件处理 每天早晨打开邮箱&#xff0c;看到堆积如山的未读邮件总让人头皮发麻。作为一个小团队的负责人&#xff0c;我经常需要处理客户咨询、内部沟通、会议邀请等各种类型的邮件。最…...

ReAct Agent:新手程序员必看!收藏这款融合推理与行动的AI智能体框架,轻松入门大模型应用开发

ReAct框架通过结合推理与行动&#xff0c;解决了传统提示工程的局限性&#xff0c;构建出能主动思考、决策并执行复杂任务的智能体。本文详细介绍了ReAct的核心设计思想&#xff0c;包括推理模块的动态思考链和错误回溯机制&#xff0c;以及行动模块的工具集成和环境状态感知。…...

OpenClaw:四大使用挑战与破局思路

子玥酱 &#xff08;掘金 / 知乎 / CSDN / 简书 同名&#xff09; 大家好&#xff0c;我是 子玥酱&#xff0c;一名长期深耕在一线的前端程序媛 &#x1f469;‍&#x1f4bb;。曾就职于多家知名互联网大厂&#xff0c;目前在某国企负责前端软件研发相关工作&#xff0c;主要聚…...

PlayCover深度技术解析:如何在M系列Mac上实现iOS游戏原生运行体验

PlayCover深度技术解析&#xff1a;如何在M系列Mac上实现iOS游戏原生运行体验 【免费下载链接】PlayCover Community fork of PlayCover 项目地址: https://gitcode.com/gh_mirrors/pl/PlayCover PlayCover作为一款创新的开源工具&#xff0c;让Apple Silicon Mac用户能…...

别啃书了!用这款70块的Steam游戏《Turing Complete》,手把手带你从逻辑门拼出CPU

从逻辑门到CPU&#xff1a;用《Turing Complete》重构计算机组成原理学习体验 当我在大学第一次翻开《计算机组成原理》教材时&#xff0c;那些密密麻麻的逻辑门符号和抽象的数据通路图让我头皮发麻。直到在Steam上发现标价70元的《Turing Complete》——这款看似简单的电路模拟…...