整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Bootstrap 缩略图

讲解 Bootstrap 缩略图。大多数站点都需要在网格中布局图像、视频、文本等。Bootstrap 通过缩略图为此提供了一种简便的方式。使用 Bootstrap 创建缩略图的步骤如下:

  • 在图像周围添加带有 class .thumbnail 的 <a> 标签。

  • 这会添加四个像素的内边距(padding)和一个灰色的边框。

  • 当鼠标悬停在图像上时,会动画显示出图像的轮廓。

下面的实例演示了默认的缩略图:

实例

<divclass="row"><divclass="col-sm-6 col-md-3"><ahref="#"class="thumbnail"><imgsrc="/wp-content/uploads/2014/06/kittens.jpg"alt="通用的占位符缩略图"></a></div><divclass="col-sm-6 col-md-3"><ahref="#"class="thumbnail"><imgsrc="/wp-content/uploads/2014/06/kittens.jpg"alt="通用的占位符缩略图"></a></div><divclass="col-sm-6 col-md-3"><ahref="#"class="thumbnail"><imgsrc="/wp-content/uploads/2014/06/kittens.jpg"alt="通用的占位符缩略图"></a></div><divclass="col-sm-6 col-md-3"><ahref="#"class="thumbnail"><imgsrc="/wp-content/uploads/2014/06/kittens.jpg"alt="通用的占位符缩略图"></a></div></div>

结果如下所示:

添加自定义的内容

现在我们有了一个基本的缩略图,我们可以向缩略图添加各种 HTML 内容,比如标题、段落或按钮。具体步骤如下:

  • 把带有 class .thumbnail 的 <a> 标签改为 <div>。

  • 在该 <div> 内,您可以添加任何您想要添加的东西。由于这是一个 <div>,我们可以使用默认的基于 span 的命名规则来调整大小。

  • 如果您想要给多个图像进行分组,请把它们放置在一个无序列表中,且每个列表项向左浮动。

下面的实例演示了这点:

实例

<divclass="row"><divclass="col-sm-6 col-md-3"><divclass="thumbnail"><imgsrc="/wp-content/uploads/2014/06/kittens.jpg"alt="通用的占位符缩略图"><divclass="caption"><h3>缩略图标签</h3><p>一些示例文本。一些示例文本。</p><p><ahref="#"class="btn btn-primary"role="button"> 按钮 </a><ahref="#"class="btn btn-default"role="button"> 按钮 </a></p></div></div></div><divclass="col-sm-6 col-md-3"><divclass="thumbnail"><imgsrc="/wp-content/uploads/2014/06/kittens.jpg"alt="通用的占位符缩略图"><divclass="caption"><h3>缩略图标签</h3><p>一些示例文本。一些示例文本。</p><p><ahref="#"class="btn btn-primary"role="button"> 按钮 </a><ahref="#"class="btn btn-default"role="button"> 按钮 </a></p></div></div></div><divclass="col-sm-6 col-md-3"><divclass="thumbnail"><imgsrc="/wp-content/uploads/2014/06/kittens.jpg"alt="通用的占位符缩略图"><divclass="caption"><h3>缩略图标签</h3><p>一些示例文本。一些示例文本。</p><p><ahref="#"class="btn btn-primary"role="button"> 按钮 </a><ahref="#"class="btn btn-default"role="button"> 按钮 </a></p></div></div></div><divclass="col-sm-6 col-md-3"><divclass="thumbnail"><imgsrc="/wp-content/uploads/2014/06/kittens.jpg"alt="通用的占位符缩略图"><divclass="caption"><h3>缩略图标签</h3><p>一些示例文本。一些示例文本。</p><p><ahref="#"class="btn btn-primary"role="button"> 按钮 </a><ahref="#"class="btn btn-default"role="button"> 按钮 </a></p></div></div></div></div>

尝试一下 »

结果如下所示:

如您还有不明白的可以在下面与我留言或是与我探讨QQ群308855039,我们一起飞!

本文中,我将展示如何使用 Node.js、Puppeteer、headless Chrome 和 Docker 从样式复杂的 React 页面生成 PDF 文档。

背景:几个月前,一个客户要求我们开发一个功能,用户可以得到 PDF 格式的 React 页面内容。该页面基本上是患者病例的报告和数据可视化结果,其中包含许多 SVG。另外还有一些特殊的请求来操纵布局,并对 HTML 元素进行一些重新排列。因此与原始的 React 页面相比,PDF 中应该有不同的样式和额外的内容。

由于这个任务比用简单的 CSS 规则解决要复杂得多,所以我们先探讨了可能的实现方法。我们找到了 3 个主要解决方案。这篇博文将指导你了解它们的可能性并最终实施。

目录:

  • 在客户端还是服务器端生成?
  • 方案1:从 DOM 制作屏幕截图
  • 方案2:仅使用 PDF 库
  • 最终方案3:Node.js、Puppeteer 和 Headless Chrome
  • 样式控制
  • 将文件发送到客户端并保存
  • 在 Docker 中使用 Puppeteer
  • 方案3 +1:CSS打印规则
  • 总结

在客户端还是服务器端生成?

在客户端和服务器端都可以生成PDF文件。但是让后端处理它可能更有意义,因为你并不想耗尽用户浏览器可以提供的所有资源。

即便如此,我仍然会展示这两种方法的解决方案。

方案1:从 DOM 制作屏幕截图

乍一看,这个解决方案似乎是最简单的,事实证明的确是这样,但它有其自身的局限性。如果你没有特殊需求,例如在 PDF 中选择文本或对文本进行搜索,那么这就是一种简单易用的方法。

此方法简单明了:从页面创建屏幕截图,并把它放到 PDF 文件中。非常直截了当。我们可以使用两个包来实现:

  • Html2canvas,根据 DOM 生成截图
  • jsPdf,一个生成PDF的库

开始编码:

npm install html2canvas jspdf

就这样!

请注意 html2canvas 的 onclone方法。当你在截图之前需要操纵 DOM(例如隐藏打印按钮)时,它是非常方便的。我看到过很多使用这个包的项目。但不幸的是,这不是我们想要的,因为我们需要在后端完成对 PDF 的创建工作。

方案2:只使用 PDF 库

NPM上有几个库,如 jsPDF(如上所述)或PDFKit。他们的问题是,如果我想使用这些库,我将不得不重新调整页面结构。这肯定会损害可维护性,因为我需要将所有后续更改应用到 PDF 模板和 React 页面中。

请看下面的代码。你需要亲自手动创建 PDF 文档。你需要遍历 DOM 并找出每个元素并将其转换为 PDF 格式,这是一项繁琐的工作。必须找到一个更简单的方法。

这段代码段来自 PDFKit 文档。但是如果你的目标是直接生成一个 PDF 文件,而不是对一个已经存在的(并且不断变化的)HTML 页面进行转换,它还是很有用的。

最终方案3:基于 Node.js 的 Puppeteer 和 Headless Chrome

什么是 Puppeteer?其文档中写道:

Puppeteer 是一个 Node 库,它提供了一个高级 API 来控制 DevTools 协议上的 Chrome 或 Chromium。 Puppeteer 默认以 headless 模式运行 Chrome 或 Chromium,但其也可以被配置为完整的(non-headless)模式运行。

它本质上是一个可以从 Node.js 运行的浏览器。如果你读过它的文档,其中首先提到的就是你可以用 Puppeteer 来生成页面的截图和PDF。优秀!这正是我们想要的。

先用 npmi i puppeteer 安装 Puppeteer,并实现我们的功能。

这是一个简单的功能,可导航到 URL 并生成站点的 PD F文件。

首先,我们启动浏览器(仅在 headless 模式下支持 PDF 生成),然后打开新页面,设置视口,并导航到提供的URL。

设置 waitUntil:'networkidle0' 选项意味着当至少500毫秒没有网络连接时,Puppeteer 会认为导航已完成。 (可以从 API docs 获取更多信息。)

之后,我们将 PDF 保存为变量,关闭浏览器并返回 PDF。

注意:page.pdf 方法接收 options 对象,你可以使用 'path' 选项将文件保存到磁盘。如果未提供路径,则 PDF 将不会被保存到磁盘,而是会得到缓冲区。(稍后我将讨论如何处理它。)

如果需要先登录才能从受保护的页面生成 PDF,首先你要导航到登录页面,检查表单元素的 ID 或名称,填写它们,然后提交表单:

要始终将登录凭据保存在环境变量中,不要硬编码!

样式控制

Puppeteer 也有这种样式操作的解决方案。你可以在生成 PDF 之前插入样式标记,Puppeteer 将生成具有已修改样式的文件。

await page.addStyleTag({ content: '.nav { display: none} .navbar { border: 0px} #print-button {display: none}' })

将文件发送到客户端并保存

好的,现在你已经在后端生成了一个 PDF 文件。接下来做什么?

如上所述,如果你不把文件保存到磁盘,将会得到一个缓冲区。你只需要把含有适当内容类型的缓冲区发送到前端即可。

现在,你只需在浏览器向服务器发送请求即可得到生成的 PDF。

一旦发送了请求,缓冲区的内容就应该开始下载了。最后一步是将缓冲区数据转换为 PDF 文件。

就这样!如果单击“保存”按钮,那么浏览器将会保存 PDF。

在 Docker 中使用 Puppeteer

我认为这是实施中最棘手的部分 —— 所以让我帮你节省几个小时的百度时间。

官方文档指出*“在 Docker 中使用 headless Chrome 并使其运行起来可能会非常棘手”*。官方文档有疑难解答部分,你可以找到有关用 Docker 安装 puppeteer 的所有必要信息。

如果你在 Alpine 镜像上安装 Puppeteer,请确保在看到页面的这一部分时再向下滚动一点。否则你可能会忽略一个事实:你无法运行最新的 Puppeteer 版本,并且你还需要用一个标记禁用 shm :

const browser = await puppeteer.launch({
 headless: true,
 args: ['--disable-dev-shm-usage']
});

否则,Puppeteer 子进程可能会在正常启动之前耗尽内存。

方案 3 + 1:CSS 打印规则

可能有人认为从开发人员的角度来看,简单地使用 CSS 打印规则很容易。没有 NPM 模块,只有纯 CSS。但是在跨浏览器兼容性方面,它的表现如何呢?

在选择 CSS 打印规则时,你必须在每个浏览器中测试结果,以确保它提供的布局是相同的,并且它不是100%能做到这一点。

例如,在给定元素后面插入一个 break-after 并不是一个多么高深的技术,但是你可能会惊讶的发现要在 Firefox 中使用它需要使用变通方法。

除非你是一位经验丰富的 CSS 大师,在创建可打印页面方面有很多的经验,否则这可能会非常耗时。

如果你可以使打印样式表保持简单,打印规则是很好用的。

让我们来看一个例子吧。

@media print {
 .print-button {
 display: none;
 }
 
 .content div {
 break-after: always;
 }
}

上面的 CSS 隐藏了打印按钮,并在每个 div 之后插入一个分页符,其中包含content 类。有一篇很棒的文章总结了你可以用打印规则做什么,以及它们有什么问题,包括浏览器兼容性。

考虑到所有因素,如果你想从不那么复杂的页面生成 PDF,CSS打印规则非常有效。

总结

让我们快速回顾前面介绍的方案,以便从 HTML 页面生成 PDF 文件:

  • 从 DOM 产生截图:当你需要从页面创建快照时(例如创建缩略图)可能很有用,但是当你需要处理大量数据时就会有些捉襟见肘。
  • 只用 PDF 库:如果你打算从头开始以编程方式创建 PDF 文件,这是一个完美的解决方案。否则,你需要同时维护 HTML 和 PDF 模板,这绝对是一个禁忌。
  • Puppeteer:尽管在 Docker 上工作相对困难,但它为我们的实现提供了最好的结果,而且编写代码也是最简单的。
  • CSS打印规则:如果你的用户受过足够的教育,知道如何把页面内容打印到文件,并且你的页面相对简单,那么它可能是最轻松的解决方案。正如你在我们的案例中所看到的,事实并非如此。

虽然今天是愚人节,但是以上所有内容都是在真的!

作者:疯狂的技术宅

链接:https://juejin.im/post/5ca1dc0251882543d569e075

hello, 大家好, 我是徐小夕, 今天又到了我们的博学时间。

本文是 100+前端几何学应用案例 专栏的第四篇文章, 之前和大家分享了如何从零实现几何画板以及几何画板的撤销重做功能:

  • 前端图形学实战: 从零开发几何画板(vue3 + vite版)
  • 前端图形学实战: 100行代码实现几何画板的撤销重做等功能(vue3 + vite版)

今天继续和大家分享一下几何画板的图层管理实时缩略图的实现。

demo演示

按照笔者的写作习惯, 这里先和大家演示一下实现的效果:

可以看到通过操作图层面板我们可以轻松的切换到某一个元素并对元素进行编辑, 同时在每次操作之后右下角的缩略图会实时展示画布最新的变动

源码地址: https://gitee.com/lowcode-china/euryd

接下来就让我们接着之前的内容, 来实现我们的图层管理面板实时缩略图

技术实现

接下来我还是用大家最最熟悉的 vue3 + ts 来实现, 其他框架实现原理类似, 感兴趣的朋友也可以举一反三, 自行实现。

image.png

图层管理面板的实现

图层管理面板主要是为了更方便管理和操作画布中的元素, 比如 PhotoShop 里的图层管理:

image.png

或者 H5-Dooring 页面制作平台的图层面板:

image.png

我们可以从这些编辑器中总结出图层管理的几个主要功能:

  • 定位或切换元素
  • 显示隐藏元素
  • 编辑元素(如删除)
  • 批量操作(如多选批量删除元素等)
  • 调整元素位置(顺序)

所以说我们在设计图层面板的时候也可以考虑以上几个点, 接下来我就来构建一下图层面板, 并实现切换元素,删除指定元素 的功能。

1. 构建图层面板

由于图层面板的元素和画布实际的元素数据是一一对应的, 所以我们可以直接用 canvasBox 来渲染图层列表, 这里回顾一下 canvasBox 的数据结构:

type shapeType = "rect" | "circle" | "line";

interface IBaseShapeProp {
  type: shapeType;
  key: string;
  style: any;
}

const canvasBox = ref<{ [key in shapeType]: IBaseShapeProp[] }>({
  rect: [],
  circle: [],
  line: [],
});

其中每个元素都包含如下三个关键属性:

  • key 元素的唯一id
  • type 元素的类型(矩形, 圆形, 线等)
  • style 元素的样式

这样我们就可以利用 key 来轻松的定位元素, 如果画布中元素很多(比如复杂的设计稿), 我们还可以给图层面板添加搜索分类功能, 方便我们更高效的定位元素。

一个简单实现的案例如下:

<div v-show="layerVisible" class="layerWrap">
  <h3>图层管理</h3>
  <div v-for="item in canvasBox.rect" :key="item.key" class="layerItem">
    <span @click.stop="handleSelected(item.key)">{{ item.key }}</span>
    <span @click="handleDelItem(item.key)"> 删除 </span>
  </div>
</div>

css样式如下:

.layerWrap {
    position: absolute;
    left: 60px;
    margin-top: -20px;
    padding-top: 10px;
    padding-bottom: 10px;
    width: 160px;
    background: #fff;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    color: #888;
    .layerItem {
      &:hover {
        background-color: rgba(110, 38, 236, 0.1);
      }
      span:last-child {
        margin-left: 20px;
      }
    }
  }

这里分享一下具体实现效果:

由于我们应用是用vue3的组合式函数写的, 上图中涉及到的切换元素和删除元素的方法也很简单, 具体如下:

import { ref } from "vue";
const curSelect = ref("");
const canvasBox = ref<{ [key in shapeType]: IBaseShapeProp[] }>({
  rect: [],
  circle: [],
  line: [],
});
// 选择元素
const handleSelected = (key: string) => {
  curSelect.value = key;
};

// 删除元素
const handleDelItem = (key: string) => {
  canvasBox.value.rect = canvasBox.value.rect.filter((v) => v.key !== key);
};

所以说图层管理的本质是基于已有的图元进行数据结构层面的操作

当然大家也可以扩展我们的画板应用, 让它支持多选, 搜索, 排列顺序等功能。

徐小夕:Dooring无代码搭建平台技术盘点10 赞同 · 4 评论文章

实时缩略图的实现

我们之前也许看过一些网站在浏览页面的时候会出现小的缩略图, 可以实时展示当前页面的情况, 比如:

image.png

这里就简单和大家分享一下实现方案。

image.png

因为我们在画布中的每一次操作都会被记录在 recordManager (记录管理器, 也就是上篇文章介绍的撤销重做的历史快照集合)中, 我们只需要在每次操作后基于当前 dom 生成一张图片即可(画布如果是canvas实现的, miniMap实现起来会更简单)。

所以说我们现在的问题就变成了如何基于 dom 生成图片快照的问题了, 当然这里也有解决方案, 核心思路就是将 dom 转换成 xml 结构,然后放在标签内,借助 svg 的处理能力将 dom 结构转换成 svg 标签,然后将svg标签作为图片的 base64 地址,最后用 a 标签实现下载。 不过需要注意以下两个细节:

  • img标签的地址必须是base64字符串, 所以我们需要用canvas转换成base64
  • canvas标签直接转成xml是无法显示的, 所以我们需要将canvas转换成base64,再放入图片的src内

通过以上方式我们就可以原生实现将 dom 转换为图片。 当然市面上也有比较成熟的方案, 比如:

  • html2canvas
  • dom2image

那这里我就用 dom2image 带大家一起实现一下 miniMap

首先我们在vite 工程中安装该库:

  yarn add dom-to-image

具体实现:

const pushRecordFn = (
  state: { [key in shapeType]: IBaseShapeProp[] },
  prevState: { [key in shapeType]: IBaseShapeProp[] }
) => {
  // 生成mini缩略图片
  domtoimage
    .toPng(boardDom?.value?.boardDom)
    .then(function (dataUrl: string) {
      miniImg.value = dataUrl;
    })
    .catch(function (error: Error) {
      console.error("脚本错误!", error);
    });

  const { snapshots, maxLimit, curIndex } = recordManager.value;
  // 如果两个状态相同, 则不推入历史记录
  if (!diff(state, snapshots[curIndex])) {
    return;
  }
  // 如果在撤销的过程中重新执行了新的操作, 则覆盖上一个状态
  if (snapshots.length - 1 !== curIndex) {
    snapshots.splice(curIndex + 1, snapshots.length);
  }
  // 超过了最大限制记录
  if (snapshots.length >= maxLimit) {
    snapshots.shift();
  }

  recordManager.value.snapshots.push(cloneDeep(state));
  recordManager.value.curIndex = recordManager.value.snapshots.length - 1;
};

pushRecordFn 函数就是我们之前在实现撤销重做功能的快照记录函数, 如果大家对撤销重做功能感兴趣的可以参考我的文章:

徐小夕:前端图形学实战: 100行代码实现几何画板的撤销重做等功能(vue3 + vite版)5 赞同 · 0 评论文章

好了, 以上就实现了我们的miniMap 缩略图功能, 演示如下:

后期规划

后面会继续围绕图形可视化来实现更多有意思的应用, 比如滑动验证码, 图形编辑器, 可视化图表等, 如果大家感兴趣, 可以参考我的github: https://gitee.com/lowcode-china/euryd

如果文章对你有帮助, 欢迎点赞评论, 让我们一起探索真正的前端技术。