vue实现仿手写稿样式,可导出成png图片

文章目录

  • 环境
  • 实现
  • 效果
  • 代码

环境

安装html2canvas,用于将指定标签下的全部子节点转换为图片

 npm install html2canvas

实现

<template>
  <div class="handwrite">
    <div id="left" class="left">
      <div id="backImg" class="backImg" :style="{ backgroundImage: `url(${defaultImgUrl})` }">
        <div id="textContent" class="textContent">

        </div>
      </div>
    </div>
    <div class="right">
      <el-input type="textarea" :autosize="{minRows: 20, maxRows: 20 }" resize="none" v-model="text"></el-input>
      <div class="setting">
        <el-row>
          <el-col :span="12"> <label>颜色: </label> <input type="number" v-model="R" min="0" max="255"> <input type="number" v-model="G" min="0" max="255"> <input type="number" v-model="B" min="0" max="255"> </el-col>
          <el-col :span="12"> <label>默认背景: </label>
            <el-radio-group v-model="defaultImgUrl">
              <el-radio :label="item.value" v-for="(item,index) in defaultImgOptions" :key="index">{{item.label}}</el-radio>
            </el-radio-group>
          </el-col>
        </el-row>
        <br>
        <el-row>
          <el-col :span="12"> <label>字体: </label>
            <!--            <input id="fontFile" type="file" name="fontFile" accept=".ttf,.tff" @change="" />-->
            <el-radio-group v-model="fontType">
              <el-radio :label="item.value" v-for="(item,index) in fontOptions" :key="index">{{item.label}}</el-radio>
            </el-radio-group>
          </el-col>
          <el-col :span="12"> <label>背景图: </label> <input id="imgFile" type="file" name="imgFile" accept="image/png, image/jpeg, image/jpg" @change="selectImg" /> </el-col>
        </el-row>
        <br>
        <el-row>
          <el-col :span="8"> <label>字体水平间距: </label> <input type="number" v-model="level" min="-10" max="50"></el-col>
          <el-col :span="8"> <label>字体竖直间距: </label> <input type="number" v-model="vertical" min="10" max="50"> </el-col>
          <el-col :span="8"> <label>字体大小: </label> <input type="number" v-model="size" min="-10" max="50"> </el-col>
        </el-row>
        <br>
        <el-row>
          <el-col :span="6"> <label>左间距: </label> <input type="number" v-model="leftPitch" min="0" max="30"></el-col>
          <el-col :span="6"> <label>右间距: </label> <input type="number" v-model="rightPitch" min="0" max="30"> </el-col>
          <el-col :span="6"> <label>上间距: </label> <input type="number" v-model="topPitch" min="0" max="30"> </el-col>
          <el-col :span="6"> <label>下间距: </label> <input type="number" v-model="bottomPitch" min="0" max="30"> </el-col>
        </el-row>
        <br>
        <el-row>
          <el-col :span="6"> <label>文字旋转角度: </label> <input type="number" v-model="rotaAngle" min="-90" max="90"></el-col>
          <el-col :span="6"> <label>背景旋转角度: </label> <input type="number" v-model="backRotaAngle" min="-90" max="90"></el-col>
        </el-row>
        <br>
        <el-row>
          <el-button @click="exportImg" style="margin-left: 80%">导出图片</el-button>
        </el-row>
      </div>
    </div>

  </div>

</template>

<script >
import html2canvas from "html2canvas";

export default {
  name: "handwrite",
  data() {
    return {
      text: "",

      R: 0,
      G: 0,
      B: 0,
      defaultImgUrl: require('@/assets/handwrite/a4.jpg'),
      backImgUrl: "",
      level: 0,
      vertical: 10,
      size: 15,

      leftPitch: 0,
      rightPitch: 0,
      topPitch: 0,
      bottomPitch: 0,

      rotaAngle: 0,
      backRotaAngle: 0,

      defaultImgOptions: [
        {
          label: "A4",
          value: require('@/assets/handwrite/a4.jpg'),
        },
        {
          label: "A4-横线(红)",
          value: require('@/assets/handwrite/a4-line.jpg'),
        },
        {
          label: "A4-横线(黑)",
          value: require('@/assets/handwrite/a4-line-black.jpg'),
        },
        {
          label: "牛皮纸",
          value: require('@/assets/handwrite/niupi.jpeg'),
        },
        {
          label: "信签纸",
          value: require('@/assets/handwrite/xz.jpeg'),
        },
      ],

      fontType: "caoshu",
      fontOptions: [
        {
          label: "字体1",
          value: "XingKai",
        },
        {
          label: "字体2",
          value: "caoshu",
        },
        {
          label: "字体3",
          value: "XingShu",
        },
      ]
    }
  },
  computed: {},
  mounted() {

  },
  watch: {
    //文本
    text(){
      var textArry = this.text.split("") || [];
      if(this.isEmpty(textArry) || textArry.length <= 0){
        return ;
      }
      let textContent = document.getElementById("textContent");
      while(textContent.hasChildNodes()) //当div下还存在子节点时 循环继续
      {
        textContent.removeChild(textContent.firstChild);
      }
      let number = 0;
      textArry.forEach((item) => {
        var span = document.createElement("span");
        if(" " == item || ' ' == item ){
          item = "&emsp;";
        }
        if("\n" == item ){
          item = "<br>";
        }
        span.innerHTML = item;
        if(this.size >=15 ){
          span.style.fontSize = Math.floor(Math.random() * (this.size - (this.size-3) + 1)) + (this.size-3)+"px";
        }
        span.style.transform = "rotate("+(Math.floor(Math.random() * (5 - (-5) + 1)) + (-5))+"deg)";
        if(number%5 == 0){
          span.style.marginLeft = (Math.floor(Math.random() * (3 - (0) + 1)) + (0)) + "px";
        }
        if(number%3 == 0){
          span.style.marginLeft = (Math.floor(Math.random() * (3 - (0) + 1)) + (0)) + "px";
        }
        if(number%9 == 0){
          span.style.marginLeft = (Math.floor(Math.random() * (3 - (0) + 1)) + (0)) + "px";
        }
        span.style.fontFamily = this.fontType;
        number = number +1;
        textContent.appendChild(span);
      });
    },
    //RGB颜色
    R(){
      let textContent = document.getElementById("textContent");
      let rgb = "rgb("+this.R+","+this.G+","+this.B+")";
      textContent.style.color = this.colorHex(rgb);
    },
    //RGB颜色
    G(){
      let textContent = document.getElementById("textContent");
      let rgb = "rgb("+this.R+","+this.G+","+this.B+")";
      textContent.style.color = this.colorHex(rgb);
    },
    //RGB颜色
    B(){
      let textContent = document.getElementById("textContent");
      let rgb = "rgb("+this.R+","+this.G+","+this.B+")";
      textContent.style.color = this.colorHex(rgb);
    },
    //文字水平间距
    level(){
      let textContent = document.getElementById("textContent");
      textContent.style.letterSpacing = this.level+"px";
    },
    //文字竖直间距
    vertical(){
      let textContent = document.getElementById("textContent");
      textContent.style.lineHeight = this.vertical+"px";
    },
    //文字大小
    size(){
      let textContent = document.getElementById("textContent");
      textContent.style.fontSize = this.size+"px";

      var textArry = this.text.split("") || [];
      if(this.isEmpty(textArry) || textArry.length <= 0){
        return ;
      }
      while(textContent.hasChildNodes()) //当div下还存在子节点时 循环继续
      {
        textContent.removeChild(textContent.firstChild);
      }
      let number = 0;
      textArry.forEach((item) => {
        var span = document.createElement("span");
        if(" " == item || ' ' == item ){
          item = "&emsp;";
        }
        if("\n" == item ){
          item = "<br>";
        }
        span.innerHTML = item;
        if(this.size >=15 ){
          span.style.fontSize = Math.floor(Math.random() * (this.size - (this.size-3) + 1)) + (this.size-3)+"px";
        }
        span.style.transform = "rotate("+(Math.floor(Math.random() * (5 - (-5) + 1)) + (-5))+"deg)";
        if(number%5 == 0){
          span.style.marginLeft = (Math.floor(Math.random() * (3 - (0) + 1)) + (0)) + "px";
        }
        if(number%3 == 0){
          span.style.marginLeft = (Math.floor(Math.random() * (3 - (0) + 1)) + (0)) + "px";
        }
        if(number%9 == 0){
          span.style.marginLeft = (Math.floor(Math.random() * (3 - (0) + 1)) + (0)) + "px";
        }
        span.style.fontFamily = this.fontType;
        number = number +1;
        textContent.appendChild(span);
      });
    },
    //字体
    fontType(){
      var textArry = this.text.split("") || [];
      if(this.isEmpty(textArry) || textArry.length <= 0){
        return ;
      }
      let textContent = document.getElementById("textContent");
      while(textContent.hasChildNodes()) //当div下还存在子节点时 循环继续
      {
        textContent.removeChild(textContent.firstChild);
      }
      let number = 0;
      textArry.forEach((item) => {
        var span = document.createElement("span");
        if(" " == item || ' ' == item ){
          item = "&emsp;";
        }
        if("\n" == item ){
          item = "<br>";
        }
        span.innerHTML = item;
        if(this.size >=15 ){
          span.style.fontSize = Math.floor(Math.random() * (this.size - (this.size-3) + 1)) + (this.size-3)+"px";
        }
        span.style.transform = "rotate("+(Math.floor(Math.random() * (5 - (-5) + 1)) + (-5))+"deg)";
        if(number%5 == 0){
          span.style.marginLeft = (Math.floor(Math.random() * (3 - (0) + 1)) + (0)) + "px";
        }
        if(number%3 == 0){
          span.style.marginLeft = (Math.floor(Math.random() * (3 - (0) + 1)) + (0)) + "px";
        }
        if(number%9 == 0){
          span.style.marginLeft = (Math.floor(Math.random() * (3 - (0) + 1)) + (0)) + "px";
        }
        span.style.fontFamily = this.fontType;
        number = number +1;
        textContent.appendChild(span);
      });
    },
    //左边距
    leftPitch(){
      let textContent = document.getElementById("textContent");
      textContent.style.paddingLeft = this.leftPitch+"px";
    },
    //右边距
    rightPitch(){
      let textContent = document.getElementById("textContent");
      textContent.style.paddingRight = this.rightPitch+"px";
    },
    //上边距
    topPitch(){
      let textContent = document.getElementById("textContent");
      textContent.style.paddingTop = this.topPitch+"px";
    },
    //下边距
    bottomPitch(){
      let textContent = document.getElementById("textContent");
      textContent.style.paddingBottom = this.bottomPitch+"px";
    },
    //字体旋转角
    rotaAngle(){
      let textContent = document.getElementById("textContent");
      textContent.style.transform = "rotate("+this.rotaAngle+"deg)";
    },
    //背景旋转角
    backRotaAngle(){
      let backImg = document.getElementById("backImg");
      backImg.style.transform = "rotate("+this.backRotaAngle+"deg)";
    }
  },
  methods: {
    //选择背景图
    selectImg(){
      //生成的临时url
      debugger
      const url = URL.createObjectURL(document.querySelector('#imgFile').files[0]);
      this.defaultImgUrl = url;
    },
    //导出图片
    exportImg(){
      html2canvas(document.querySelector("#backImg")
        ,{
          //高度和宽度 背景色
          // width: 75,
          // height: 75,
          // backgroundColor: "#00ff00",
          //使用 scale 属性可以修改渲染时的放大倍数(默认为 1),将其调大可以解决低分辨率设备下生成的图片模糊问题。
          scale: 2,
          //指定渲染的 Canvas 如果页面上原先就有个 canvas 元素,我们希望可以将图片绘制在它上面,可以使用 canvas 属性设置。
          // canvas: document.querySelector("#myCanvas")
        }
      ).then(canvas => {
        // 将canvas转换成img的src流
        // var imgUrl = canvas.toDataURL("image/png");
        // console.log("base64编码数据:", imgUrl);
        // 此方法可以设置截图质量(0-1)
        // var imgUrl = canvas.toDataURL("image/png", 1);
        // console.log("base64编码数据:", imgUrl);
        //将canvas内容保存为文件并下载
        canvas.toBlob(function(blob) {
          saveAs(blob, "手写稿.png");
        });
      });

    },
    /**
     *
     * @param 校验是否为空
     */
    isEmpty(obj) {
      if (obj == undefined || obj == null || obj == '' || obj == "" ) {
        return true
      }
      return false
    },

    /**
     * RGB转换为16进制
     * @returns {string}
     */
    colorHex(color) {
      // RGB颜色值的正则
      var reg = /^(rgb|RGB)/;
      if (reg.test(color)) {
        var strHex = "#";
        // 把RGB的3个数值变成数组
        var colorArr = color.replace(/(?:\(|\)|rgb|RGB)*/g, "").split(",");
        // 转成16进制
        for (var i = 0; i < colorArr.length; i++) {
          var hex = Number(colorArr[i]).toString(16);
          if (hex === "0") {
            hex += hex;
          }
          strHex += hex;
        }
        return strHex;
      } else {
        return String(color);
      }
    },

    /**
     * 16进制转换为RGB
     * @returns {string}
     */
    colorRgb(color) {
      // 16进制颜色值的正则
      var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
      // 把颜色值变成小写
      var color = color.toLowerCase();
      if (reg.test(color)) {
        // 如果只有三位的值,需变成六位,如:#fff => #ffffff
        if (color.length === 4) {
          var colorNew = "#";
          for (var i = 1; i < 4; i += 1) {
            colorNew += color.slice(i, i + 1).concat(color.slice(i, i + 1));
          }
          color = colorNew;
        }
        // 处理六位的颜色值,转为RGB
        var colorChange = [];
        for (var i = 1; i < 7; i += 2) {
          colorChange.push(parseInt("0x" + color.slice(i, i + 2)));
        }
        return "RGB(" + colorChange.join(",") + ")";
      } else {
        return color;
      }
    }

  }
}
</script>


<style scoped lang="scss">
.handwrite{
  width: 100%;
  height: 100%;


  .left{
    background-color: #c01111;
    float: left;
    margin-left: 3%;
    width: 45%;
    height: 98vh;
    padding: 5px;
    margin-bottom: 10px;

    .backImg{
      width: 100%;
      height: 100%;
      -moz-background-size:100% 100%;
      background-size:100% 100%;
    }

    .textContent {
      margin-top: 0%;
    }
  }

  .right{
    background-color: #1ab394;
    float: left;
    margin-left: 3%;
    width: 45%;
    height: 98vh;
    padding: 5px;
    margin-bottom: 10px;

    .setting{
      margin-top: 5px;
    }
  }
}


</style>



效果

在这里插入图片描述

代码

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++ | set与map的用法指南

前言 前面我们学习了vector、list等容器&#xff0c;其实他们都属于序列式容器&#xff0c;因为其底层为线性结构&#xff1b;今天我们学习使用的set与map是属于关联式容器&#xff0c;关联式容器更注重于数据检索访问的效率&#xff1b;本文所有的资料均查阅于文档&#xff0c…

RocketMQ深入分析

RocketMQ深入分析 1. 消息存储 目前的MQ中间件从存储模型来&#xff0c;分为需要持久化和不需要持久化的两种模型&#xff0c;现在大多数的是支持持久化存储的&#xff0c;比如ActiveMQ、RabbitMQ、Kafka、RocketMQ&#xff0c;ZeroMQ却不需要支持持久化存储而业务系统也大多…

BART模型和 Electra模型对比

总结 Electra模型在使用较少的计算资源的情况下能够达到跟大语言模型相近的效果。但BART模型对于传统的BERT中加入了不同种制造noise的方式&#xff0c;是BERT和GPT的结合体。Electra模型主要是Generator模型和Discriminator模型的结合体。 未知参数设置&#xff0c;两个模型…

【历史上的今天】7 月 20 日:人类登上月球;数据仓库之父诞生;Mac OS X Lion 发布

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 7 月 20 日&#xff0c;在 2005 年的今天&#xff0c;时任微软全球副总裁的李开复加盟谷歌担任谷歌全球副总裁及中国区总裁。谷歌公司在发布聘请李开复消息的同…

Rust之通用编程

1、变量与可变性&#xff1a; 在Rust语言中&#xff0c;变量默认是不可变的&#xff0c;所以一旦变量被绑定到某个值上面&#xff0c;这个值就再也无法被改变。 可以通过在声明的变量名称前添加mut关键字来使其可变。除了使变量的值可变&#xff0c;mut还会向阅读代码的人暗示…

【机器学习】分类算法 - KNN算法(K-近邻算法)KNeighborsClassifier

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;零基础快速入门人工智能《机器学习入门到精通》 K-近邻算法 1、什么是K-近邻算法&#xff1f;2、K-近邻算法API3、…

【论文阅读 03】机器学习算法在颈动脉斑块影像学分类中的研究进展

读完之后就是&#xff0c;总结 机器学习&#xff08;SVM、小波&#xff09;和深度学习&#xff08;CNN&#xff09;在 颈动脉斑块影像学中的 分类效果。只讨论了超声、磁共振两种成像 Chin J Clin Neurosci 临床神经科学杂志 复旦大学 颈动脉斑块( carotid plaques) 是一种…

opencv 之 外接多边形(矩形、圆、三角形、椭圆、多边形)使用详解

opencv 之 外接多边形&#xff08;矩形、圆、三角形、椭圆、多边形&#xff09;使用详解 本文主要讲述opencv中的外接多边形的使用&#xff1a; 多边形近似外接矩形、最小外接矩形最小外接圆外接三角形椭圆拟合凸包 将重点讲述最小外接矩形的使用 1. API介绍 #多边形近似 v…

若依微服务整合activiti7.1.0.M6

若依微服务3.6.3版本整合activiti7&#xff08;7.1.0.M6&#xff09; 目前有两种办法集成activiti7 放弃activiti7新版本封装的API&#xff0c;使用老版本的API&#xff0c;这种方式只需要直接集成即可&#xff0c;在7.1.0.M6版本中甚至不需要去除security的依赖。不多介绍&a…

Origin科学绘图分析软件2023最新版下载安装教程

在科学研究和工程领域&#xff0c;数据的处理和分析是至关重要的一环&#xff0c;而Origin则是这方面的一款重要工具。Origin软件是由OriginLab公司开发的&#xff0c;主要用于各种科学数据的处理和分析&#xff0c;以及高质量的科学图形的创建。#乐享周末分享吧# 下载地址文末…

【英杰送书第三期】Spring 解决依赖版本不一致报错 | 文末送书

Yan-英杰的主 悟已往之不谏 知来者之可追 C程序员&#xff0c;2024届电子信息研究生 目录 问题描述 报错信息如下 报错描述 解决方法 总结 【粉丝福利】 【文末送书】 目录&#xff1a; 本书特色&#xff1a; 问题描述 报错信息如下 Description:An attempt…

微服务保护——Sentinel【实战篇二】

一、线程隔离 &#x1f349; 线程隔离有两种方式实现&#xff1a; 线程池隔离信号量隔离&#xff08;Sentinel默认采用&#xff09; 线程隔离&#xff08;舱壁模式&#xff09;&#x1f95d; 在添加限流规则时&#xff0c;可以选择两种阈值类型&#xff1a; QPS&#xff1a;…

Java反射 -- 详细介绍 (框架核心)

反射 是 Java框架 的核心 &#xff0c;无论是Tomcat、SpringMVC、Spring IOC、Spring AOP、动态代理 &#xff0c;都使用到了 反射 反射的作用简单讲就是 无需 new 对象&#xff0c;就可以动态获取到一个类的全部信息&#xff0c;包括 属性、方法&#xff0c;构造器&#xff0…

3、C# 方法构成

上一节,我们讲述了程序的基本构成。由大到小分别为”解决方案-->项目-->类-->方法“。 这一节,我们讲讲方法。 方法可以说是程序的基本构成单位。假如把方法抽象成点的话,我们可以认为程序是一个树状的结构。树根,就是我们的起点方法,也叫主方法。这一点,基本…

iOS 测试 iOS 端 Monkey 测试

说起 Monkey 测试&#xff0c;大家想到的是 monkey 测试只有安卓有&#xff0c;monkey 测试只针对安卓 app&#xff0c;今天给大家分享一下 Monkey 测试在 iOS 端也能跑&#xff01;iOS 端 app 也能使用 Monkey 测试来执行稳定性测试。 一、环境准备 1、准备 Mac 设备&#x…

SpringCloud分布式项目下feign的使用

新建一个feign的微服务&#xff08;后面统称为A&#xff09;&#xff0c;其他项目要使用利用maven导入该服务模块的依赖就行了 导入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</…

Tabby - 本地化AI代码自动补全 - Linux Debian

参考&#xff1a; https://github.com/TabbyML/tabby Docker | Tabby Linux Debian上快速安装Docker并运行_Entropy-Go的博客-CSDN博客 Tabby - 本地化AI代码自动补全 - Windows10_Entropy-Go的博客-CSDN博客 为什么选择Tabby 已经有好几款类似强劲的代码补全工具&#xf…

【苹果日历推送】群发部署开发工具、SDK或框架,如APNs推送服务的HTTP/2接口

苹果日历本身并不直接支持群发推送通知&#xff0c;因为推送通知是针对单个设备的。如果你想向多个用户发送推送通知&#xff0c;你需要在自己的应用中实现推送功能&#xff0c;然后针对每个设备单独发送推送通知。 以下是实现推送通知的一般步骤&#xff1a; 开发推送服务&a…

【Linux | Shell】结构化命令2 - test命令、方括号测试条件、case命令

目录 一、概述二、test 命令2.1 test 命令2.2 方括号测试条件2.3 test 命令和测试条件可以判断的 3 类条件2.3.1 数值比较2.3.2 字符串比较 三、复合条件测试四、if-then 的高级特性五、case 命令 一、概述 上篇文章介绍了 if 语句相关知识。但 if 语句只能执行命令&#xff0c…

vscode(Better Comments插件)在vue文件中不显示相对应的颜色

解决办法&#xff1a; 1、在.vscode文件下找到 aaron-bond.better-comments-3.0.2 &#xff08;我的路径&#xff1a;C:\Users\cown\.vscode\extensions\aaron-bond.better-comments-3.0.2&#xff09;&#xff0c;后面版本不唯一&#xff0c;根据自身情况辨别 2、进入文件路…