html5-qrcode实现移动端浏览器扫描二维码&条形码

1,149 阅读4分钟

html5-qrcode实现移动端浏览器扫描二维码&条形码

  • 2026了,更新文档,解决一些实际问题
  • 原先扫码功能确实会存在横屏无法扫码,切换至 html5-qrcode 即可实现轻松扫码
  • https协议还是必须的,正式环境可以生成本地ssl证书。本地开发可以用插件,本地ssl证书或者直接开发都行,正式环境必须https协议

1、https协议

1.1 方案一:使用插件,适合本地开发调试

  1. 移动端访问摄像头权限需要 https 协议
  2. 安装@vitejs/plugin-basic-ssl并配置https协议 本地开发使用
pnpm i @vitejs/plugin-basic-ssl -D

vite.config.ts

import basicSsl from '@vitejs/plugin-basic-ssl'

export default { 
server: { 
https: true 
}, 
plugins: [ basicSsl() ] 
}

1.2 方案二:生成ssl证书,适合本地开发或者测试环境等,正式环境替换为正式证书即可

要生成适配Nginx的本地可信 SSL 证书,推荐用 mkcert 工具,步骤简单且能避免浏览器 “不安全” 提示,全程基于 Windows 系统:

  • 1.1.1.188 是测试IP地址,实际IP地址填服务器或者自己的IP地址即可

前提准备

  1. 下载 mkcert访问 mkcert 官网 Releases,下载 Windows 版本(如 mkcert-v1.4.4-windows-amd64.exe)。将文件重命名为 mkcert.exe,放到 C:\mkcert 文件夹下(手动新建该文件夹)。

  2. 配置环境变量右键「此电脑」→ 属性 → 高级系统设置 → 环境变量 → 系统变量 → 找到 Path → 编辑 → 新建 → 输入 C:\mkcert → 确定保存。

  3. 验证安装打开普通终端(无需管理员),执行:

    运行

    mkcert --version
    

    能显示版本号即安装成功。

生成证书(适配你的 IP 和域名)

  1. 进入 Nginx 的 cert 目录你的 Nginx 根目录是 D:\Desktop\nginx,先创建 cert 文件夹并进入:

    运行

    cd D:\Desktop\nginx
    mkdir cert  # 若已存在则跳过
    cd cert
    
  2. 生成证书执行命令,包含你需要的 IP 和本地域名(和 Nginx 配置的 server_name 一致):

    运行

    mkcert 1.1.1.188 localhost 127.0.0.1 ::1
    

    执行后,cert 文件夹会生成两个文件:

    • 证书文件:1.1.1.188+3.pem+3 是自动生成的数字,以实际为准)
    • 私钥文件:1.1.1.188+3-key.pem
  3. 信任根证书执行命令安装本地 CA 根证书,浏览器会自动信任生成的证书:

    运行

    mkcert -install
    
    

2、使用证书和开启https协议 ( 方案二扩展 )

2.1 本地开发

  • 在 vite.config.ts 配置 https 证书路径即可

vite.config.ts

// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import path from 'path'
import compression from 'vite-plugin-compression'
import fs from 'fs';
//自动导入ui-组件 比如说ant-design-vue  element-plus等
import Components from 'unplugin-vue-components/vite';
//ant-design-vue
import { AntDesignVueResolver } from "unplugin-vue-components/resolvers"


export default defineConfig(({ command, mode }) => {
  let env = loadEnv(mode, process.cwd())
  return {
    server: {
      https: {
        cert: fs.readFileSync(path.resolve(__dirname, './1.1.1.188+3.pem')), // 证书文件路径
        key: fs.readFileSync(path.resolve(__dirname, './1.1.1.188+3-key.pem')) // 私钥文件路径
      },
      host: true,
      open: false,
      proxy: {
      },
    },
  
  }
})

2.1 nginx 部署

  • 其中 cerf 用于存放 ssl 证书 image.png

  • 配置

    server {
        listen       9001 ssl;  # 关键:9001 端口直接启用 HTTPS(而非 HTTP 重定向)
        server_name  localhost 1.1.1.188;  # 匹配证书中的域名/IP

        
        # 证书配置(与之前一致)
        ssl_certificate      ../cert/1.1.1.188+3.pem;
        ssl_certificate_key  ../cert/1.1.1.188+3-key.pem;
        
        # SSL 优化配置(保留)
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
        ssl_prefer_server_ciphers on;
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 10m;

        location = /Test {
            root   html;
            try_files /KaBo/index.html =404;
        }

        # ========== ,刷新不404 ==========
        location /Test/ {
            root   html;
            index  Test/index.html;
            # 关键:所有 /Test/ 开头的请求,都重定向到 KaBo/index.html(交给前端路由)
            try_files $uri $uri/ /Test/index.html;
        }

        location / {
            root   html;
            index  Test/index.html;
            try_files $uri $uri/ /Test/index.html;  
        }


        location /kaboApi/ {  
            proxy_pass http://1.1.1.81:18888/;  
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme; 
            proxy_connect_timeout 30s; 
            proxy_send_timeout 60s;
            proxy_read_timeout 60s;
            proxy_buffering on;  
        }


        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

  • 通过 nginx -t 检查配置是否有问题,没有直接执行重载命令 nginx -s reload 即可
  • 有问题可以交给AI修复

3、封装 html5-qrcode 组件

  • 基于 html5-qrcode 分装成组件,方便调用
  • 处于扫码组件内部是阻止浏览器默认返回上一页的默认行为,改为销毁组件

3.1 组件完整代码

<template>
    <div class="scanCode">
        <div id="reader"></div>
        <a-button class="btn" @click="closeScan" shape="circle" :icon="h(CloseOutlined)">
        </a-button>
    </div>
</template>

<script setup lang="ts">
import { ref, h, onMounted } from 'vue'
import { Html5Qrcode } from 'html5-qrcode'
import { CloseOutlined } from '@ant-design/icons-vue';

const codeContent = ref('1024')
const $emits = defineEmits(['cancel', 'scanResult'])
let html5: any = null

Html5Qrcode.getCameras()
    .then((devices) => {
        const html5QrCode = new Html5Qrcode('reader')
        const width = window.innerWidth
        const height = window.innerHeight
        const aspectRatio = width / height
        const reverseAspectRatio = height / width

        const mobileAspectRatio =
            reverseAspectRatio > 1.5
                ? reverseAspectRatio + (reverseAspectRatio * 12) / 100
                : reverseAspectRatio
        html5 = html5QrCode
        if (devices && devices.length) {
            html5QrCode
                .start(
                    { facingMode: { exact: 'environment' } },
                    {
                        fps: 10,
                        aspectRatio: aspectRatio + 1,
                        qrbox: { width: 250, height: 250 },
                        videoConstraints: {
                            facingMode: 'environment',
                            aspectRatio:
                                width < 600 ? mobileAspectRatio : aspectRatio,
                        },
                    },
                    (decodedText, decodedResult) => {
                        codeContent.value = decodedText
                        $emits('scanResult', decodedText)
                        html5QrCode.stop()
                    },
                    (errorMessage) => {
                    }
                )
                .catch((err) => {
                })
        }
    })
    .catch((err) => {
        // handle err
        console.log(err)
    })

const closeScan = () => {
    if (html5) {
        html5.stop()
    }
    $emits('cancel')
}

onMounted(() => {
    // 禁止返回上一页 返回上一页是触发关闭扫码
    history.pushState('', '', document.URL)
    window.addEventListener('popstate', function (e) {
        if (html5) {
            html5.stop()
        }
        $emits('cancel')
    })
})

</script>

<style lang="scss" scoped>
.scanCode {
    width: 100dvw;
    height: 100dvh;
    display: flex;
    flex-direction: column;
    background: rgba(0, 0, 0);
    position: fixed;
    top: 0;
    left: 0;
    z-index: 9999;

    #reader {
        top: 50%;
        left: 0;
        transform: translateY(-50%);
    }

    .btn {
        position: absolute;
        top: 20px;
        right: 20px;
    }
}
</style>

3.2 使用案例

同时支持PC、pad、移动端

<template>
 <ScanQrCodePad v-if="scanCoding" @cancel="scanCoding = false" @scanResult="scanResult">
 </ScanQrCodePad>
</template>

<script setup lang="ts">
import {ref} from 'vue'
// TODO: 全局注册或者直接导入组件


// 控制是否扫码
const scanCoding = ref(false)

// 扫描结果输出
const scanResult = (val:any)=>{
    console.log(val)
}


</script>

<style scoped>

</style>
2.jpg 1.jpg