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

news2024/11/20 20:27:31

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>
        <TextField
          value={config.title}
          onChange={e => handleConfigChange(e.target.value, 'title')}
          placeholder='标题'
          size='small'
          multiline
          rows={3}
          fullWidth
        />
      </Box>
      <Box className={styles.setItem}>
        <Typography variant="h5">作者</Typography>
        <TextField
          value={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' }}>
          <Select
            value={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/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1401938.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【OCR项目】之用HALCON的深度学习工具进行文字识别,并导出到C++调用

前言 HALCON是一个强大的机器视觉工具&#xff0c;包含了2D&#xff0c;3D图像各种算子&#xff0c;以及各种任务的深度学习工具&#xff0c;包括目标检测&#xff0c;实例分割&#xff0c;文字识别等。 这次从实际生产的角度&#xff0c;来分享一下如何用HALCON进行文字识别…

基于 IDEA 创建 Maven 工程

1. 概念梳理Maven工程的GAVP Maven工程相对之前的项目&#xff0c;多出一组gavp属性&#xff0c;gav&#xff08;表示当前工程的坐标&#xff09;需要我们在创建项目的时候指定&#xff0c;p&#xff08;表示打包方式&#xff09;有默认值&#xff08;默认为 jar 包&#xff0…

web架构师编辑器内容-拖动元素改变元素的位置和大小的完成

拖动移动元素 改变编辑器的定位系统 我们目前的元素都是按照块级元素直接自上而下的排列在画布中&#xff0c;为了让元素实现精确的定位和调整&#xff0c;我们需要改变这些元素的定位实现。我们需要让这些元素画布区域来进行绝对定位。如果我们有一个元素有这些已经保存的 c…

第15届蓝桥杯嵌入式省赛准备第三天总结笔记(使用STM32cubeMX创建hal库工程+串口接收发送)

因为我是自己搞得板子&#xff0c;原本的下程序和串口1有问题&#xff0c;所以我用的是串口2&#xff0c;用的PA2和PA3 一&#xff0c;使用CubeMX配置串口 选择A开头的这个是异步通信。 配置串口参数&#xff0c;往届的题基本用的9600波特率&#xff0c;所以我这里设置为9600…

【Linux】Ubuntu的gnome切换KDE Plasma

文章目录 安装KDE Plasma桌面环境添加软件源并更新apt安装kubuntu-desktop&#xff08;作者没有成功&#xff09;aptitude安装kubuntu-desktop多次aptitude install&#xff08;特别重要特别重要&#xff09;其他kde软件包 卸载gnome桌面 Ubuntu自带的桌面环境是gnome&#xff…

cuda二进制文件中到底有些什么

大家好。今天我们来讨论一下&#xff0c;相比gcc编译器编译的二进制elf文件&#xff0c;包含有 cuda kernel 的源文件编译出来的 elf 文件有什么不同呢&#xff1f; 之前研究过一点 tvm。从 BYOC 的框架中可以得知&#xff0c;前端将模型 partition 成 host 和 accel(accel 表…

《WebKit 技术内幕》之六(2): CSS解释器和样式布局

2 CSS解释器和规则匹配 在了解了CSS的基本概念之后&#xff0c;下面来理解WebKit如何来解释CSS代码并选择相应的规则。通过介绍WebKit的主要设施帮助理解WebKit的内部工作原理和机制。 2.1 样式的WebKit表示类 在DOM树中&#xff0c;CSS样式可以包含在“style”元素中或者使…

【QT+QGIS跨平台编译】之四:【libSSH2+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

文章目录 一、libSSH2介绍二、文件下载三、文件分析四、pro文件五、编译实践 一、libSSH2介绍 libSSH2是一个开源的C函数库&#xff0c;用来实现SSH2协议。 SSH(Secure SHell)到目前为止有两个不兼容的版本——SSH1和SSH2。 SSH2避免了RSA的专利问题&#xff0c;并修补了CRC…

C#winform上位机开发学习笔记2-串口助手的定时发送功能添加

1.功能描述 选择自动发送功能后&#xff0c;按照设定的发送时间发送数据 2.代码部分 增加计时器空间Timer 使能计时器&#xff0c;默认设置定时时间为1秒 组合框设置默认复选信息 编写选择框事件函数 //自动发送事件private void checkBox27_CheckedChanged(object sender, E…

37-WEB漏洞-反序列化之PHPJAVA全解(上)

WEB漏洞-反序列化之PHP&JAVA全解&#xff08;上&#xff09; 一、PHP 反序列化原理二、案例演示2.1、无类测试2.1.1、本地2.1.2、CTF 反序列化小真题2.1.3、CTF 反序列化类似题 2.2、有类魔术方法触发2.2.1、本地2.2.2、网鼎杯 2020 青龙大真题 三、参考资料 一、PHP 反序列…

Excel新建文件打开后提示文件扩展名与文件格式不匹配

环境&#xff1a; Win10专业版 excel2016 问题描述&#xff1a; Excel新建文件打开后提示文件扩展名与文件格式不匹配 解决方案&#xff1a; 1.调出注册表编辑器&#xff0c;按层点击文件夹&#xff1a;HKEY_CURRENT_USER/Software/Microsoft/Office/12.0/Excel/Securit…

PSoc62™开发板之rtc时间获取

实验目的 1.使用PSoc62™芯片读取内部rtc时间 2.OLED屏幕显示当前时间戳 实验准备 PSoc62™开发板SSD1306 OLED模块公母头杜邦线 芯片资源 PSoC 6系列MCU时钟系统由以下几部分组成&#xff0c;PSoc62™开发板没有接外部时钟源&#xff0c;所以只能从IMO、ILO、PILO里边配…

Linux 命令大全 CentOS常用运维命令

文章目录 1、Linux 目录结构2、解释目录3、命令详解3.1、shutdown命令3.1、文件目录管理命令ls 命令cd 命令pwd 命令tree 命令mkdir 命令touch 命令cat 命令cp 命令more 命令less 命令head 命令mv 命令rm 命令ln 命令tail 命令cut命令 3.2、用户管理useradd/userdel 命令用户的…

STM32标准库——(2)GPIO输出

1.GPIO简介 GPIO&#xff08;General Purpose Input Output&#xff09;通用输入输出口可配置为8种输入输出模式引脚电平&#xff1a;0V~3.3V&#xff0c;部分引脚可容忍5V输出模式下可控制端口输出高低电平&#xff0c;用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等输入模…

小程序学习-20

建议每次构建npm之前都先删除miniprogram_npm

2024最新版Python 3.12.1安装使用指南

2024最新版Python 3.12.1安装使用指南 Installation and Configuration Guide to the latest version Python 3.12.1 in 2024 By Jackson Python编程语言&#xff0c;已经成为全球最受欢迎的编程语言之一&#xff1b;它简单易学易用&#xff0c;以标准库和功能强大且广泛外挂…

Docker(十)Docker Compose

作者主页&#xff1a; 正函数的个人主页 文章收录专栏&#xff1a; Docker 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01; Docker Compose 项目 Docker Compose 是 Docker 官方编排&#xff08;Orchestration&#xff09;项目之一&#xff0c;负责快速的部署分布式…

2024PMP考试新考纲-【过程领域】近期典型真题和很详细解析(9)

华研荟继续为您分享【过程Process领域】的新考纲下的真题&#xff0c;帮助大家体会和理解新考纲下PMP的考试特点和如何应用所学的知识和常识&#xff08;经验&#xff09;来解题&#xff0c;并且举一反三&#xff0c;一次性3A通过2024年PMP考试。 2024年PMP考试新考纲-【过程领…

智能算法 | Matlab实现改进黑猩猩优化算法SLWCHOA与多个基准函数对比与秩和检验

智能算法 | Matlab实现改进黑猩猩优化算法SLWCHOA与多个基准函数对比与秩和检验 目录 智能算法 | Matlab实现改进黑猩猩优化算法SLWCHOA与多个基准函数对比与秩和检验预测效果基本描述程序设计参考资料 预测效果 基本描述 1.Matlab实现改进黑猩猩优化算法SLWCHOA与多个基准函数…

Spring Boot3整合Druid(监控功能)

目录 1.前置条件 2.导依赖 错误依赖&#xff1a; 正确依赖&#xff1a; 3.配置 1.前置条件 已经初始化好一个spring boot项目且版本为3X&#xff0c;项目可正常启动。 作者版本为3.2.2最新版 2.导依赖 错误依赖&#xff1a; 这个依赖对于spring boot 3的支持不够&#…