前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Canvas 进阶(一)二维码的生成与扫码识别

Canvas 进阶(一)二维码的生成与扫码识别

作者头像
小皮咖
发布于 2019-11-05 07:13:38
发布于 2019-11-05 07:13:38
2.8K00
代码可运行
举报
文章被收录于专栏:小皮咖小皮咖
运行总次数:0
代码可运行

背景

前些日子当前端面试官,问了一个问题:“你了解过canvas吗?”

“这个我知道,我有做过DEMO,这个不难吧,看着它的api接口就能实现!”

看着他这么(蜜汁)自信,我决定深入了解(为难)一下他!

“电商中大转盘,九宫格,刮刮乐,如何使用canvas实现,讲讲你的思路?”

“二维码的生成和扫码识别如何实现?”

“图片的粒子爆炸效果呢?”

“......”


因此,打算写一系列关于 canvas 的文章,探索学习提升自己的同时顺便分享给大家。

二维码的生成

二维码的生成需借助第三方库,利用其算法对文本转化成二维码,并用 canvas 绘画出来。利用 canvas.toDataURL('image/png') 获取二维码转 base64 值,再将其赋值给 img 标签的 src 属性

这里我使用了一个库,qrcodejs.

可点击 《Demo》 查看效果

使用方法如下:

代码语言:javascript
代码运行次数:0
运行
复制
<!-- index.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Suporka Vue App</title>
    <style>
      .container {
        padding: 60px;
        margin: 0 auto;
        line-height: 50px;
      }
      input {
        display: inline-block;
        width: 200px;
        height: 32px;
        line-height: 1.5;
        padding: 4px 7px;
        font-size: 12px;
        border: 1px solid #dcdee2;
        border-radius: 4px;
        color: #515a6e;
        background-color: #fff;
        background-image: none;
        position: relative;
        cursor: text;
        transition: border 0.2s ease-in-out, background 0.2s ease-in-out,
          box-shadow 0.2s ease-in-out;
      }
      button {
        color: #fff;
        background-color: #19be6b;
        border-color: #19be6b;
        outline: 0;
        vertical-align: middle;
        line-height: 1.5;
        display: inline-block;
        font-weight: 400;
        text-align: center;
        -ms-touch-action: manipulation;
        touch-action: manipulation;
        cursor: pointer;
        background-image: none;
        border: 1px solid transparent;
        white-space: nowrap;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
        padding: 5px 15px 6px;
        font-size: 12px;
        border-radius: 4px;
        transition: color 0.2s linear, background-color 0.2s linear,
          border 0.2s linear, box-shadow 0.2s linear;
      }
      #qrcode {
        margin-top: 20px;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <input
        type="text"
        placeholder="请输入您想转化成二维码的字符串"
        id="input"
      />
      <button onclick="creatQRcode();">一键生成</button>
      <div id="qrcode"></div>
    </div>
    <script src="https://zxpsuper.github.io/Demo/qrcode/qrcode-dev.js"></script>
    <script type="text/javascript">
      var qrcode = null;
      function creatQRcode() {
        document.getElementById("qrcode").innerHTML = "";
        qrcode = new QRCode(document.getElementById("qrcode"), {
          text: document.getElementById("input").value,
          width: 200,
          height: 200,
          colorDark: "#000000",
          colorLight: "#ffffff",
          correctLevel: QRCode.CorrectLevel.H
        });
      }
    </script>
  </body>
</html>
复制代码

options

  • 首参为生成存放 img 标签的父元素
  • 第二个参数为 Object 类型的 options

属性

类型

说明

text

String

目标文本

width

Number

图片宽度

height

Number

图片高度

colorDark

String

二维码颜色

colorLight

默认QRCode.CorrectLevel.L

容错级别,可设置为:QRCode.CorrectLevel.L ,QRCode.CorrectLevel.M,QRCode.CorrectLevel.Q,QRCode.CorrectLevel.H

二维码扫码识别

这里利用了一个库 llqrcode.js, 使用 qrcode.decode() 对 id 为 qr-canvascanvas 进行解码.

先上 Demo项目源码

我们需要做的就是,调用设备的摄像头(后置摄像头优先),获得的画面用 video 标签实时显示出来,再定时取画面生成 canvas ,调用 qrcode.decode() 解密。

代码语言:javascript
代码运行次数:0
运行
复制
// variable.js
var gCtx = null; //canvas.ctx
var gCanvas = null; // qr-canvas
// var c = 0;
var stype = 0; // 识别流程 0未开始 1进行中 2已结束
var gUM = false;
var webkit = false;
var moz = false;
var v = null; // 存放视频的变量
var scanCodeStart = false; // 开始扫码
var mediaStreamTrack = null; // mediaStreamTrack 实现关闭摄像头功能 mediaStreamTrack.stop()
var imghtml =
  '<div id="qrfile"><canvas id="out-canvas" width="320" height="240"></canvas>' +
  '<div id="imghelp">drag and drop a QRCode here' +
  "<br>or select a file" +
  '<input type="file" onchange="handleFiles(this.files)" id="upload-img"/>' +
  "</div>" +
  "</div>";

var vidhtml = '<video id="v" autoplay muted></video>';
复制代码
代码语言:javascript
代码运行次数:0
运行
复制
// methods.js

function qrcodeScanLoad(width, height) {
  if (isCanvasSupported() && window.File && window.FileReader) {
    initCanvas(width, height);
    qrcode.callback = scanCodeCallback;
    document.getElementById("mainbody").style.display = "inline";
    setwebcam();
  } else {
    document.getElementById("mainbody").style.display = "inline";
    document.getElementById("mainbody").innerHTML =
      '<p id="mp1">QR code scanner for HTML5 capable browsers</p><br>' +
      '<br><p id="mp2">sorry your browser is not supported</p><br><br>';
  }
}

// 绘制遮罩层canvas
function setMask() {
  var canvas = document.querySelector("#scancode-mask");
  canvas.width =
    document.body.clientWidth > 1024 ? 1024 : document.body.clientWidth;
  canvas.height =
    document.body.clientWidth > 1024 ? 1136 : document.body.clientHeight;
  var ctx = canvas.getContext("2d");

  ctx.fillRect(0, 0, canvas.width, canvas.height);

  ctx.globalCompositeOperation = "destination-out";
  ctx.beginPath();
  let x1,
    y1,
    width = canvas.width * 0.6;
  x1 = (canvas.width - width) / 2;
  y1 = (canvas.height - width) / 2;
  ctx.fillRect(x1, y1, width, width);
  ctx.fill();
  ctx.save();

  ctx.globalCompositeOperation = "source-over";

  // 第二象限点
  ctx.moveTo(x1, y1 + 2);
  ctx.lineTo(x1 + 20, y1 + 2);
  ctx.moveTo(x1 + 2, y1);
  ctx.lineTo(x1 + 2, y1 + 20);

  // 第一象限点
  ctx.moveTo(x1 + width - 20, y1 + 2);
  ctx.lineTo(x1 + width, y1 + 2);
  ctx.moveTo(x1 + width - 2, y1 + 1);
  ctx.lineTo(x1 + width - 2, y1 + 20);

  // 第四象限点
  ctx.moveTo(x1 + width - 20, y1 + width - 2);
  ctx.lineTo(x1 + width, y1 + width - 2);
  ctx.moveTo(x1 + width - 2, y1 + width - 1);
  ctx.lineTo(x1 + width - 2, y1 + width - 20);

  // 第三象限点
  ctx.moveTo(x1 + 20, y1 + width - 2);
  ctx.lineTo(x1, y1 + width - 2);
  ctx.moveTo(x1 + 2, y1 + width - 2);
  ctx.lineTo(x1 + 2, y1 + width - 20);
  ctx.lineWidth = 4;
  ctx.strokeStyle = "green";
  ctx.stroke();
}
function setwebcam() {
  var options = true;
  if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
    try {
      navigator.mediaDevices.enumerateDevices().then(function(devices) {
        let video = [];
        devices.forEach(function(device) {
          if (device.kind === "videoinput") {
            video.push(device);
          }
        });
        // 调用设备的摄像头,video[1] 为后置摄像头,或者label含有‘back’为后置摄像头
        if (video.length >= 2) {
          options = {
            deviceId: { exact: video[1].deviceId },
            facingMode: { exact: "environment" }
          };
        }
        video.forEach(item => {
          if (item.label.toLowerCase().search("back") > -1)
            options = {
              deviceId: { exact: device.deviceId },
              facingMode: { exact: "environment" }
            };
        });
        scanCodeStart = true;
        setwebcam2(options);
      });
    } catch (e) {
      console.error(e);
    }
  } else {
    console.log("no navigator.mediaDevices.enumerateDevices");
    setwebcam2(options);
  }
}

function setwebcam2(options) {
  if (stype == 1) {
    setTimeout(captureToCanvas, 500);
    return;
  }

  var n = navigator;
  document.getElementById("outdiv").innerHTML = vidhtml;
  v = document.getElementById("v");
  try {
    if (n.mediaDevices && n.mediaDevices.getUserMedia) {
      n.mediaDevices
        .getUserMedia({ video: options, audio: false })
        .then(function(stream) {
          success(stream);
        })
        .catch(function(error) {
          error(error);
        });
    } else if (n.getUserMedia) {
      webkit = true;
      n.getUserMedia({ video: options, audio: false }, success, error);
    } else if (n.webkitGetUserMedia) {
      webkit = true;
      n.webkitGetUserMedia({ video: options, audio: false }, success, error);
    }
  } catch (err) {
    console.log(err);
  }
  stype = 1;
  if (getSystem() === "ios") {
    alert("您的設備暫不支持實時掃碼,請上傳圖片識別!");
    return;
  }
  if (
    (n.mediaDevices && n.mediaDevices.getUserMedia) ||
    n.getUserMedia ||
    n.webkitGetUserMedia
  )
    setTimeout(captureToCanvas, 500);
  else {
    alert("您的設備暫不支持實時掃碼,請上傳圖片識別!");
  }
}

// 获取操作系统
function getSystem() {
  var u = navigator.userAgent;
  var isAndroid = u.indexOf("Android") > -1 || u.indexOf("Linux") > -1; //g
  var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
  if (isAndroid) {
    //这个是安卓操作系统
    return "android";
  } else if (isIOS) {
    //这个是ios操作系统
    return "ios";
  } else {
    return "other";
  }
}

// 选择图片上传
function setimg($event) {
  qrcode.callback = scanCodeCallback;
  $event && $event.preventDefault();
  stype = 2;
  let file = document.getElementById("upload-img");
  file.click();
}

// 上传成功的回调
function scanCodeCallback(a) {
  var html = htmlEntities(a);
  stype = 0;
  alert(html);
}

// 处理上传文件识别
function handleFiles(f) {
  var o = [];

  for (var i = 0; i < f.length; i++) {
    var reader = new FileReader();
    reader.onload = (function(theFile) {
      return function(e) {
        gCtx.clearRect(0, 0, gCanvas.width, gCanvas.height);
        qrcode.decode(e.target.result);
      };
    })(f[i]);
    reader.readAsDataURL(f[i]);
  }
}

function initCanvas(w, h) {
  gCanvas = document.getElementById("qr-canvas");
  gCanvas.style.width = w + "px";
  gCanvas.style.height = h + "px";
  gCanvas.width = w;
  gCanvas.height = h;
  gCtx = gCanvas.getContext("2d");
  gCtx.clearRect(0, 0, w, h);
}

// 画面转 canvas
function captureToCanvas() {
  if (stype != 1) return;
  if (gUM && scanCodeStart) {
    try {
      gCtx.drawImage(v, 0, 0);
      try {
        qrcode.decode(); // 默认为id=qr-canvas的canvas转成图片的base64
      } catch (e) {
        console.log(e);
        setTimeout(captureToCanvas, 500);
      }
    } catch (e) {
      console.log(e);
      setTimeout(captureToCanvas, 500);
    }
  }
}

// 对特殊符号进行处理
function htmlEntities(str) {
  return String(str)
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;");
}

// 判断是否支持canvas
function isCanvasSupported() {
  var elem = document.createElement("canvas");
  return !!(elem.getContext && elem.getContext("2d"));
}

function success(stream) {
  // mediaStreamTrack 实现关闭摄像头功能
  if (stream)
    mediaStreamTrack =
      typeof stream.stop === "function" ? stream : stream.getTracks()[0];

  v.srcObject = stream;
  if (scanCodeStart) {
    v.play();
    gUM = true;
    setTimeout(captureToCanvas, 500);
  } else {
  }
}

function error(error) {
  gUM = false;
  return;
}
复制代码

其中 setMask 为绘制遮罩层方法,qrcodeScanLoad 为初始化加载方法。

代码语言:javascript
代码运行次数:0
运行
复制
<body>
    <div class="body">
      <div id="mainbody" style="display: inline;">
        <div id="outdiv" autoplay muted></div>
      </div>
      <canvas id="qr-canvas" width="800" height="600"></canvas>
      <canvas id="scancode-mask"></canvas>
      <div class="scancode-tips-group" id="scancode-tips-group">
        <span class="tips">QR Code 放入框内,即可自動掃描</span>
        <div class="upload-my-code" onClick="setimg()">我的QR Code</div>
      </div>

      <div id="img-upload-container" style="display: none">
        <div id="qrfile">
          <canvas id="out-canvas" width="320" height="240"></canvas>
          <div id="imghelp">
            drag and drop a QRCode here
            <br />or select a file
            <input
              type="file"
              onchange="handleFiles(this.files)"
              id="upload-img"
            />
          </div>
        </div>
      </div>
    </div>
    <script src="./llqrcode.js"></script>
    <script src="./variable.js"></script>
    <script src="./methods.js"></script>
    <script>
      qrcodeScanLoad(320, 400);
      setMask();
      // 对我的 QR Code 进行定位显示
      if (document.body.clientWidth < 1025) {
        document.getElementById("scancode-tips-group").style.top =
          (document.body.clientHeight - document.body.clientWidth) / 2 +
          document.body.clientWidth * 0.9 -
          10 +
          "px";
      } else {
        document.getElementById("scancode-tips-group").style.top = "720px";
      }
    </script>
  </body>
复制代码

这里做了适配,当超过 1024 时,canvas 宽度就设为 1024。详细在代码中已经做了注释。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019年06月12日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
原 canvas小案例集合(小画板、画的回
作者:汪娇娇 日期:2016.12.8 在现在这个公司呆了4个多月,也是研究了canvas将近4个月,前两周心里就痒痒的想写这方面的博客,但一直没时间。可一直拖着也不是个办法,就这样抽抽空来写吧。 c
jojo
2018/05/03
1.4K1
原                                                                                canvas小案例集合(小画板、画的回
Canvas 从进阶到退学
接着 《Canvas 从入门到劝朋友放弃(图解版)》 ,本文继续补充 canvas 基础知识点。
德育处主任
2022/12/13
2.1K0
Canvas 从进阶到退学
17·灵魂前端工程师养成-JavaScript实现canvas画板
-多年互联网运维工作经验,曾负责过大规模集群架构自动化运维管理工作。 -擅长Web集群架构与自动化运维,曾负责国内某大型金融公司运维工作。 -devops项目经理兼DBA。 -开发过一套自动化运维平台(功能如下): 1)整合了各个公有云API,自主创建云主机。 2)ELK自动化收集日志功能。 3)Saltstack自动化运维统一配置管理工具。 4)Git、Jenkins自动化代码上线及自动化测试平台。 5)堡垒机,连接Linux、Windows平台及日志审计。 6)SQL执行及审批流程。 7)慢查询日志分析web界面。
DriverZeng
2022/09/26
1.8K0
17·灵魂前端工程师养成-JavaScript实现canvas画板
canvas绘制折线路径动画
要实现以上路径动画,一般可以使用svg的动画功能。或者使用canvas绘制,结合路径数学计算来实现。
用户3158888
2022/03/22
1.7K0
canvas绘制折线路径动画
熬夜总结了 “HTML5画布” 的知识点(共10条)
(xStart,yStart)是线段的起点,(xEnd,yEnd)是线段终点。起点到终点之间的颜色呈渐变。
小灰
2020/08/02
7.5K0
三天学会HTML5——SVG和Canvas的使用
在第一天学习了HTML5的一些非常重要的基本知识,今天将进行更深层学习 首先来回顾第一天学习的内容,第一天学习了新标签,新控件,验证功能,应用缓存等内容。 第2天将学习如何使用Canvas 和使用SVG 实现功能 Lab1—— 使用Canvas Canvas 是指定了长度和宽度的矩形画布,我们将使用新的HTML5 JavaScript,可使用HTML5 JS API 来画出各种图形。 初始化 1. 创建HTML页面 <html> <head></head> <body></body> </html> 2.
葡萄城控件
2018/01/10
2.8K0
三天学会HTML5——SVG和Canvas的使用
HTML5(五)——Canvas API
Canvas API(画布)提供了一个通过 javascript 和 html 的 canvas 元素来在网页上实时绘制图形的方式。可用于动画、游戏、图标、图片编辑等多个方面。
呆呆
2021/09/29
7340
Canvas基础教程(章节2)
  < canvas>会创建一个固定大小的画布,会公开一个或多个 渲染上下文(画笔),使用 渲染上下文来绘制和处理要展示的内容。   我们重点研究 2D渲染上下文。其他的上下文我们暂不研究,比如, WebGL使用了基于OpenGL ES的3D上下文 (“experimental-webgl”) 。 //获得 2d 上下文对象 var ctx = canvas.getContext (‘2d’); 检测支持性 if(canvas.getContext){   var ctx = canvas.getContext(‘2d’);   // drawing code here } else{   // canvas-unsupported code here }
我不是费圆
2020/10/10
9620
Canvas基础教程(章节2)
canvas 系列学习笔记三《样式和颜色》
通过上下文可以设置strokeStyle (线条颜色) 和 fillStyle (填充颜色)。 设置完之后画线和填充就是设置的颜色。
星宇大前端
2022/09/28
6040
canvas 系列学习笔记三《样式和颜色》
Canvas
http://www.w3c.org/TR/2dcontext/ https://html.spec.whatwg.org/
jinghong
2020/05/09
13.2K0
Canvas
canvas的api的学习(一)
Canvas是 HTML5 新增的,一个可以使用脚本(通常为JavaScript)在其中绘制图像的 HTML 元素。它可以用来制作
前端老鸟
2019/07/31
4320
Canvas 上实现坐标定位
我们顺便还显示了一个拖拽的功能,当然这个并不是使用 canvas 绘制,后面会讲到。
Jimmy_is_jimmy
2023/09/01
4670
Canvas 上实现坐标定位
Canvas
1.基本使用方法 <canvas id="demo">对不起,您的浏览器不支持canvas</canvas> <!--牢记,canvas不要再CSS里面设置宽高--> //-------------------------------------- <script type="text/javascript"> /*第一步,获取canvas标签*/ var can = document.getElementById("demo"); can.style.border = '1p
天天_哥
2018/09/29
1.3K0
用 Javascript 生成二维码
大家好,这将是一篇很短的文章,我将展示如何为 JavaScript 中的任何内容生成二维码。
全栈程序员站长
2022/11/19
5790
第154天:canvas基础(一)
​ <canvas> 是 HTML5 新增的,一个可以使用脚本(通常为JavaScript)在其中绘制图像的 HTML 元素。它可以用来制作照片集或者制作简单(也不是那么简单)的动画,甚至可以进行实时视频处理和渲染。
半指温柔乐
2018/09/11
8050
第154天:canvas基础(一)
第155天:canvas(二)
​ 这个属性影响到 canvas 里所有图形的透明度,有效的值范围是 0.0 (完全透明)到 1.0(完全不透明),默认是 1.0。
半指温柔乐
2018/09/11
5090
第155天:canvas(二)
HTML5 学习总结(四)——canvas绘图、WebGL、SVG
一、Canvas canvas是HTML5中新增一个HTML5标签与操作canvas的javascript API,它可以实现在网页中完成动态的2D与3D图像技术。<canvas> 标记和 SVG以及
张果
2018/01/04
10K0
HTML5 学习总结(四)——canvas绘图、WebGL、SVG
canvas 系列学习笔记二《绘制图形》
strokeRect(x, y, width, height) 绘制一个矩形的边框
星宇大前端
2022/09/08
3070
canvas 系列学习笔记二《绘制图形》
手写原生代码专题 | 简易手写画板(二)
如视频所示,在这个示例中,我们用到了画布 canvas 相关的知识,比如创建画布、画圆形、画直线的基础知识,有了这些基础后,我们就能轻松完成本示例,示例效果如下视频所示。
前端达人
2021/07/16
1.6K0
Canvas 绘制坐标系中的点以及折线
上一篇章介绍了如何使用Canvas绘制坐标系,那么本篇章来看看怎么简单绘制坐标系中的点。
Devops海洋的渔夫
2020/02/13
1.8K0
相关推荐
原 canvas小案例集合(小画板、画的回
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验