如何实现这样一个代码图片生成器

这篇文章我来讲解下如何实现下面这样一个代码编辑器导出图片的工具。

应用地址:spacexcode.com/codeimage

介绍

要学好一门技术,最好的方式就是实践 。上面这样一个代码图片生成器,就是当初为了学习 React 技术开发,特地找的一个功能不是很复杂,但是涉及的技术点又不至于太单一, 于是找了这样一个工具型的项目(UI 参照 ray.so),代码从零开始实现来检验自己学的知识是否牢固。

简单来讲解下,实现这个项目用到的一些技术点和背后的原理:

  • 代码高亮选择了最主流的 highlight.js 库;
  • 网页元素界面转图片使用了 html2canvas 工具;
  • 代码编辑器实时高亮是上层使用了 textarea 输入框,设置字体和背景透明,下层使用 div 显示代码,并结合 highlight.js 做代码高亮;
  • 透明背景通过 CSS 属性背景图片设置线性渐变 linear-gradient 模拟实现;
  • 代码背景框的左右拖拽来改变宽度功能。

实现详解

代码编辑器

这里的难点,可能有人就想如何实现一个代码实时编辑的区域,又能让代码高亮显示?

代码编辑区域我们使用文本输入框 textarea, 代码高亮我们使用 highlight.js 帮助实现,但是如何对输入框中的代码进行高亮是个难点。这里我们实现的思路是通过绝对定位 ,上层使用输入框,设置背景透明和其中的文字颜色透明,然后下层放置一个 div 层做代码的显示。上层输入,下层显示。

结构代码

jsx 复制代码
export default function Main () {
  // ...
return (
  <div className="codeEditor">
    <textarea
      className="editorTextarea"
      spellCheck={false}
      autoComplete='off'
      tabIndex={-1}
      autoCorrect='off'
      autoCapitalize='off'
      value={code}
      onChange={e => setCode(e.target.value)}
      style={{ height: editorHeight + 'px' }}
      onInput={e => setEditorHeight(e.target.scrollHeight)}>
    </textarea>
    <div
      id={settings.lang}
      ref={codeRef}
      className={clsx("codeFormatted", settings.lang)}
      style={{ color: '#fefdfd', background: 'transparent' }}
    >
      {code}
    </div>
  </div>
)

样式代码

css 复制代码
.codeEditor {
  display: grid;
  width: 100%;
  grid-template: 1fr/1fr;
}
.editorTextarea {
  border: none;
  resize: none;
  background: transparent;
  z-index: 2;
  color: transparent;
  caret-color: white;
  text-size-adjust: none;
}
.editorTextarea, .codeFormatted {
  padding: 16px 16px 21px 16px;
  margin: 0;
  font: var(--ifm-font-size-base) / var(--ifm-line-height-base) var(--ifm-font-family-base);
  font-weight: 500;
  font-family: 'JetBrains Mono', monospace;
  font-variant-ligatures: none;
  grid-column: 1/1;
  grid-row: 1/1;
  tab-size: 2;
  white-space: pre-wrap;
  box-sizing: border-box;
}

从 CSS 代码中高亮的部分我们可以看出,针对 editorTextareacodeFormatted 两个类设置了相同的字体类型、大小相同的样式,就是保证 代码的输入层和显示层上下相同文字处于完全重合的状态,来模拟看到的代码就是你实时输入的代码。

highlight.js 实现代码高亮部分代码:

jsx 复制代码
import hljs from '../../config/highlight';

useEffect(() => {
  if (codeRef.current) {
    hljs.highlightElement(codeRef.current);
  }
}, [code, settings.lang])

代码背景区域拖拽改变宽度

本功能我已经将核心代码抽出来,实现成了小组件。原理不难懂,就是通过添加页面元素的监听事件,按下鼠标之后跟随鼠标移动位置,来计算区块宽度改变后的大小。

详细实现参考:【可左右拖拽改变大小的区块

将页面元素转成图片导出

得益于 html2canvas 工具库的帮助,我们很轻松将网页中某一部分通过 canvas 中转导出成图片。

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

透明背景的模拟实现

当我们将设置区域的背景按钮切换置灰时,显示出透明的样式:

这个其实是 通过 CSS 模拟出来的

css 复制代码
.grid {
  height: 200px;
  background-image:
    linear-gradient(45deg, #8d8b8b 25%, transparent 0),
    linear-gradient(-45deg, #8d8b8b 25%, transparent 0),
    linear-gradient(45deg, transparent 75%, #8d8b8b 0),
    linear-gradient(-45deg, transparent 75%, #8d8b8b 0);
  background-position: 0 0, 0 10px, 10px -10px, -10px 0;
  background-size: 20px 20px;
}

最后

核心的功能实现的思路都讲解了,然后剩下细节部分需要完善的,比如主题,我们制作了8种好看的渐变颜色背景。通过改变内边距,来改变代码区域占整个图片的比例。

总结

实现一款好用的小工具,不仅仅要掌握实现的基本思路,核心代码的实现。后期的细节完善,页面的样式和用户的操作体验都是值得细细推敲的。经过这个工具的制作,基本掌握了一个框架 的大部分语法的使用。

比如:

  • React 中样式代码的几种写法
  • 表单中变量的响应式
  • 常见 hooks 的使用,比如:useCallback,useState,useEffect 等
  • 组件化的代码
相关推荐
也无晴也无风雨23 分钟前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang1 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational2 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
阮少年、4 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
郝晨妤5 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
AvatarGiser6 小时前
《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明
前端·vue.js·elementui
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js
别忘了微笑_cuicui6 小时前
elementUI中2个日期组件实现开始时间、结束时间(禁用日期面板、控制开始时间不能超过结束时间的时分秒)实现方案
前端·javascript·elementui