查看原文
其他

so easy...自制代码截图工具

码中人 码农真经 2023-12-25
因为Talk is cheap. Show me the code,写编程的文章,往往都要附上代码片段。
文章中插入代码块普遍有3种形式:
  1. 代码文本,通过pre或code标签插入
  2. codepen.io 或gist 等工具
  3. 代码截图
今天讨论的就是第3种形式——代码截图的方式。

演示




http://codeimg.mzh.ren/

现行工具的问题




现行生成的代码截图的工具很多,效果也很好,深受大家喜爱。这里列举一些大家比较常用的:
  • Carbon | Create and share beautiful images of your source code
  • Codeimg.io
  • text2image
  • Instacode
  • CodeKeep.io – Save & Organize code snippets
  • Create beautiful images of your code
  • Polacode – Visual Studio Marketplace
  • CodeSnap – Visual Studio Marketplace
以上工具都非常好用实用,但也有一些缺点:

名字记不住

以上这么多工具,我只能记住codeimg.io,所以我自己写的工具也叫 codeimg:http://codeimg.mzh.ren/。
像最有名的Carbon,它的网页链接是:https://carbon.now.sh/,压根记不住。
虽然可以存储在书签栏,但还增加了查找成本。

丢失编辑器代码样式

其实我们每天用的代码编辑器的主题,应该就是需要的代码高亮、配色等。
然而从编辑器拷贝到网页版的工具中,编辑器的样式就不翼而飞了,取而代之是工具提供样式。
如果你的博客是深色风格,你需要代码截图是浅色风格,默认的风格如果是深色,就冲突了。
所以还要花时间去选择适当的风格,增加了时间成本。

功能不全

对,功能不全。
大部分工具都有核心功能,如格式化、外框、语法高亮等。
但总会丢一些小功能,如文件标题、水印、字体大小等。如Carbon就没有水印。

配置复杂且配置不持久

有些工具非常复杂,把所有细节都配置化了。如:codeimg.io。
好不容易配置好样式、水印等,结果下次打开,又得重新配置,闹心!
并且,大而全的工具,加载相对较慢。

适用性不强

有些工具作为vscode的插件,但该插件又不一定会有pycharm或idea的版本。
网页版的适用性强一些。


定制代码截图工具




原理

实现代码截图的过程主要为3步:
  1. 在网页中放置(富)文本编辑器(如textarea)
  2. 粘贴入代码
  3. 将页面渲染成图片下载

(富)文本编辑器

在HTML中,任何元素都可以被编辑。为了使元素可编辑,你所要做的就是在html标签上设置”contenteditable”属性,它几乎支持所有的HTML元素。
Content Editable – Web 开发者指南 | MDN

contentEditable也是大家最为喜闻乐见的富文本编辑器实现方案,部分基础功能由浏览器实现。

这种方式能记住源格式的样式,如设置元素#editor-content的contentEditable属性为true,结合样式,就能产生一个编辑器。
再将该编辑的html源码粘贴上去,就达到以下效果:

html2canvas

html2canvas – Screenshots with JavaScript
html2canvas.js是一个将html dom转化成图片的 js 类库。
html2canvas.js 通过读取 DOM 和应用元素上的样式,将当前页面或具体元素呈现为画布图像。
它不需要来自服务器的任何渲染,因为整个图像是在客户端的浏览器上创建的。但是,正因为它严重依赖于浏览器,因此该库不适合在 nodejs 中使用。它也不会神奇地绕过任何浏览器内容策略限制,因此呈现跨域内容将需要代理才能将内容获取到相同的来源。

实现代码

通过reactjs构建了一个小网页。
App.js
样式文件App.css
界面效果如下,真是一言难尽…
贴入代码,点击下载按钮,调用codeimg方法,即可下载。
其中CodeImg.js即是对html2canvas.js进行简单封装:
实现很简单,脏活累活都让html2canvas.js干完了,感谢html2canvas.js。
希望能为你提供思路,定制属于自己的代码截图器。

完整代码




App.js
import { useState } from 'react';import CodeImg from './CodeImg';import './App.css';
function App() { const [fontsize, setFontsize] = useState(1); const [name, setName] = useState("代码截图"); const [watermark, setWatermark] = useState("码中人 http://www.mzh.ren/");
const nameHandle = (e) => setName(e.target.value); const watermarkHandle = (e) => setWatermark(e.target.value); const clearCode = () => document.getElementById('editor-content').innerHTML = "";
return ( <div className="App">
<h1>简单代码截图</h1>
<div className="controls"> <button onClick={() => { if (fontsize < 4) { setFontsize(fontsize + 1) } }}>字体放大</button> <button onClick={() => { if (fontsize > 1) { setFontsize(fontsize - 1) } }}>字体缩小</button> <button onClick={ clearCode }>清空</button> <button onClick={() => CodeImg('#editor', name)}>下载</button> </div>
<div className="editor-wrapper" id="editor"> <div className="editor-header"> <input type="text" value={name} onChange={nameHandle} className="name" /> <div class="editor-window--actions"> <span class="editor-window--action exit"></span> <span class="editor-window--action minimize"></span> <span class="editor-window--action maximize"></span> </div> </div>
<div contentEditable="true" className={"x" + fontsize} id="editor-content"> </div>
<div className="editor-footer"> <input type="text" className="watermark" value={watermark} onChange={watermarkHandle} /> </div> </div>
</div> );}
export default App;

App.css
body { padding: 0 1em;}
button { margin-right: 10px;}
button:last-child { color: red;}
.editor-wrapper { position: relative; padding: 1em; margin-top: 1em; background-color: rgba(0, 0, 0, 0.2); width: fit-content;}
/* ----editor header----- */.editor-header { position: absolute; top: 1.4em; width: 100%;}
.editor-window--actions { position: absolute; top: 0; left: 1em; width: 80px;}

.editor-window--action { display: inline-block; width: 12px; height: 12px; border-radius: 50%; margin-right: 6px;}
.editor-window--action.exit { background-color: #ff5f56; -webkit-box-shadow: 0 0 1px #e0443e; box-shadow: 0 0 1px #e0443e;}
.editor-window--action.minimize { background-color: #ffbd2e; -webkit-box-shadow: 0 0 1px #dea123; box-shadow: 0 0 1px #dea123;}
.editor-window--action.maximize { background-color: #27c93f; -webkit-box-shadow: 0 0 1px #1aab29; box-shadow: 0 0 1px #1aab29;}
.watermark,.name { background: transparent; border: 0 none; color: #fff; width: 100%; text-align: center;}
.watermark { text-align: right; color: cornsilk;}
/* ----- editor content ----- */
#editor-content { min-width: 20em; min-height: 10em; outline: 1px dashed gray;}
#editor-content>div { padding: 1em; padding-top: 3em; color: #fff; background-color: transparent; border-radius: 1em;}
.x2,.x2>div { font-size: 1.2rem; line-height: 2rem;}
.x3,.x3>div { font-size: 1.5rem; line-height: 2rem;}
.x4,.x4>div { font-size: 2rem; line-height: 2.4rem;}
/* --- editor content --- */
.editor-footer { position: absolute; bottom: 1.2em; right: 1.4em; width: 100%;}

CodeImg.js
import html2canvas from 'html2canvas';
export default function exportImg(selector,name="代码截图"){ html2canvas(document.querySelector(selector), { // 转换为图片 useCORS: true // 解决资源跨域问题 }).then(canvas => { let imgUrl = canvas.toDataURL('image/png'); downloadIamge(imgUrl, name + ".png") })}
function downloadIamge(imgsrc, name) { let image = new Image(); image.setAttribute("crossOrigin", "anonymous"); image.onload = function () { let canvas = document.createElement("canvas"); canvas.width = image.width; canvas.height = image.height; let context = canvas.getContext("2d"); context.drawImage(image, 0, 0, image.width, image.height); let url = canvas.toDataURL("image/png"); //得到图片的base64编码数据 let a = document.createElement("a"); // 生成一个a元素 let event = new MouseEvent("click"); // 创建一个单击事件 a.download = name || "photo"; // 设置图片名称 a.href = url; // 将生成的URL设置为a.href属性 a.dispatchEvent(event); // 触发a的单击事件 }; image.src = imgsrc;}

往期推荐

开源软件的兴起【中英字幕】

数学指南:实用数学手册

【致谢】感谢群友微信读书助力得到《深入理解计算机系统》

Markdown完全教程

历史悠久的著名英语学习教材《看图学英语》简介(pdf、mp3、mp4)

继续滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存