search
Vue star Featured

Vue.js Scan & Generate QRCodes in Browser

Quick guide to generating and scanning QR codes in Vue.js browser applications. Simple examples with qrcode.vue and html5-qrcode.

person By Gautam Sharma
calendar_today December 31, 2024
schedule 11 min read
Vue QR Code Browser Scanner

Generate and scan QR codes directly in Vue.js applications. Works in the browser with no backend required.

Installation

npm install qrcode.vue3
npm install html5-qrcode

Generate QR Codes

Basic QR Code

<!-- QRGenerator.vue -->
<script setup>
import { ref } from 'vue'
import QrcodeVue from 'qrcode.vue3'

const qrValue = ref('https://example.com')
const qrSize = ref(256)
const qrLevel = ref('M')
</script>

<template>
  <div class="qr-generator">
    <h2>Generate QR Code</h2>

    <input
      v-model="qrValue"
      type="text"
      placeholder="Enter text or URL"
      class="input-field"
    />

    <div class="qr-display">
      <qrcode-vue
        :value="qrValue"
        :size="qrSize"
        :level="qrLevel"
      />
    </div>

    <button @click="downloadQR">Download QR Code</button>
  </div>
</template>

<style scoped>
.qr-generator {
  max-width: 500px;
  margin: 0 auto;
  padding: 20px;
  text-align: center;
}

.input-field {
  width: 100%;
  padding: 12px;
  margin: 20px 0;
  border: 2px solid #ddd;
  border-radius: 8px;
  font-size: 16px;
}

.qr-display {
  margin: 20px 0;
  padding: 20px;
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

button {
  padding: 12px 24px;
  background: #42b883;
  color: white;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-size: 16px;
}

button:hover {
  background: #35a372;
}
</style>

Download QR Code

<script setup>
import { ref } from 'vue'
import QrcodeVue from 'qrcode.vue3'

const qrValue = ref('https://example.com')

const downloadQR = () => {
  const canvas = document.querySelector('canvas')
  if (canvas) {
    const url = canvas.toDataURL('image/png')
    const link = document.createElement('a')
    link.download = 'qrcode.png'
    link.href = url
    link.click()
  }
}
</script>

<template>
  <div class="qr-generator">
    <input v-model="qrValue" placeholder="Enter URL" />

    <qrcode-vue
      :value="qrValue"
      :size="256"
      level="H"
    />

    <button @click="downloadQR">Download</button>
  </div>
</template>

Scan QR Codes

Camera Scanner

<!-- QRScanner.vue -->
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { Html5QrcodeScanner } from 'html5-qrcode'

const scannedResult = ref('')
const isScanning = ref(false)
let scanner = null

const onScanSuccess = (decodedText) => {
  scannedResult.value = decodedText
  console.log('QR Code scanned:', decodedText)

  // Stop scanning after success
  if (scanner) {
    scanner.clear()
    isScanning.value = false
  }
}

const onScanError = (error) => {
  console.warn('Scan error:', error)
}

const initScanner = () => {
  scanner = new Html5QrcodeScanner(
    'qr-reader',
    {
      fps: 10,
      qrbox: { width: 250, height: 250 }
    },
    false
  )

  scanner.render(onScanSuccess, onScanError)
  isScanning.value = true
}

const restartScanner = () => {
  scannedResult.value = ''
  initScanner()
}

onMounted(() => {
  initScanner()
})

onUnmounted(() => {
  if (scanner) {
    scanner.clear()
  }
})
</script>

<template>
  <div class="scanner-container">
    <h2>Scan QR Code</h2>

    <div id="qr-reader"></div>

    <div v-if="scannedResult" class="result">
      <h3>Scanned Result:</h3>
      <p>{{ scannedResult }}</p>
      <button @click="restartScanner">Scan Again</button>
    </div>
  </div>
</template>

<style scoped>
.scanner-container {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

#qr-reader {
  border: 2px solid #ddd;
  border-radius: 8px;
  overflow: hidden;
  margin: 20px 0;
}

.result {
  margin-top: 20px;
  padding: 20px;
  background: #f0f9ff;
  border-radius: 8px;
  border: 2px solid #42b883;
}

.result h3 {
  margin: 0 0 10px 0;
  color: #42b883;
}

.result p {
  word-break: break-all;
  padding: 10px;
  background: white;
  border-radius: 4px;
  margin: 10px 0;
}

button {
  padding: 12px 24px;
  background: #42b883;
  color: white;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-size: 16px;
}

button:hover {
  background: #35a372;
}
</style>

Combined Component

<!-- QRManager.vue -->
<script setup>
import { ref } from 'vue'
import QRGenerator from './QRGenerator.vue'
import QRScanner from './QRScanner.vue'

const activeTab = ref('generate')
</script>

<template>
  <div class="qr-manager">
    <div class="tabs">
      <button
        :class="{ active: activeTab === 'generate' }"
        @click="activeTab = 'generate'"
      >
        Generate QR Code
      </button>
      <button
        :class="{ active: activeTab === 'scan' }"
        @click="activeTab = 'scan'"
      >
        Scan QR Code
      </button>
    </div>

    <QRGenerator v-if="activeTab === 'generate'" />
    <QRScanner v-if="activeTab === 'scan'" />
  </div>
</template>

<style scoped>
.qr-manager {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.tabs {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

.tabs button {
  flex: 1;
  padding: 12px;
  border: 2px solid #ddd;
  background: white;
  cursor: pointer;
  border-radius: 8px;
  font-size: 16px;
}

.tabs button.active {
  background: #42b883;
  color: white;
  border-color: #42b883;
}
</style>
<!-- QRWithLogo.vue -->
<script setup>
import { ref } from 'vue'
import QrcodeVue from 'qrcode.vue3'

const qrValue = ref('https://example.com')
const logoUrl = ref('/logo.png')
</script>

<template>
  <div class="qr-logo-container">
    <input
      v-model="qrValue"
      placeholder="Enter URL"
      class="input-field"
    />

    <div class="qr-wrapper">
      <qrcode-vue
        :value="qrValue"
        :size="300"
        level="H"
      />
      <img
        :src="logoUrl"
        alt="Logo"
        class="qr-logo"
      />
    </div>
  </div>
</template>

<style scoped>
.qr-logo-container {
  text-align: center;
  padding: 20px;
}

.qr-wrapper {
  position: relative;
  display: inline-block;
  margin: 20px 0;
}

.qr-logo {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 60px;
  height: 60px;
  background: white;
  padding: 5px;
  border-radius: 8px;
}
</style>

Dynamic QR Generation

vCard QR Code

<!-- VCardQR.vue -->
<script setup>
import { ref, computed } from 'vue'
import QrcodeVue from 'qrcode.vue3'

const name = ref('')
const phone = ref('')
const email = ref('')

const vCardData = computed(() => {
  if (!name.value) return ''

  return `BEGIN:VCARD
VERSION:3.0
FN:${name.value}
TEL:${phone.value}
EMAIL:${email.value}
END:VCARD`
})
</script>

<template>
  <div class="vcard-qr">
    <h3>Generate vCard QR Code</h3>

    <input v-model="name" placeholder="Name" class="input" />
    <input v-model="phone" placeholder="Phone" class="input" />
    <input v-model="email" placeholder="Email" class="input" />

    <div v-if="vCardData" class="qr-display">
      <qrcode-vue
        :value="vCardData"
        :size="256"
        level="M"
      />
    </div>
  </div>
</template>

<style scoped>
.vcard-qr {
  max-width: 500px;
  margin: 0 auto;
  padding: 20px;
}

.input {
  width: 100%;
  padding: 10px;
  margin: 10px 0;
  border: 2px solid #ddd;
  border-radius: 8px;
}

.qr-display {
  margin-top: 20px;
  text-align: center;
}
</style>

WiFi QR Code

<!-- WiFiQR.vue -->
<script setup>
import { ref, computed } from 'vue'
import QrcodeVue from 'qrcode.vue3'

const ssid = ref('')
const password = ref('')
const encryption = ref('WPA')

const wifiData = computed(() => {
  if (!ssid.value) return ''
  return `WIFI:T:${encryption.value};S:${ssid.value};P:${password.value};;`
})
</script>

<template>
  <div class="wifi-qr">
    <h3>WiFi QR Code</h3>

    <input
      v-model="ssid"
      placeholder="Network Name (SSID)"
      class="input"
    />

    <input
      v-model="password"
      type="password"
      placeholder="Password"
      class="input"
    />

    <select v-model="encryption" class="input">
      <option value="WPA">WPA/WPA2</option>
      <option value="WEP">WEP</option>
      <option value="nopass">No Password</option>
    </select>

    <div v-if="wifiData" class="qr-display">
      <qrcode-vue
        :value="wifiData"
        :size="256"
        level="H"
      />
    </div>
  </div>
</template>

<style scoped>
.wifi-qr {
  max-width: 500px;
  margin: 0 auto;
  padding: 20px;
}

.input {
  width: 100%;
  padding: 10px;
  margin: 10px 0;
  border: 2px solid #ddd;
  border-radius: 8px;
}

.qr-display {
  margin-top: 20px;
  text-align: center;
}
</style>

Scan from File

<!-- FileScannerQR.vue -->
<script setup>
import { ref } from 'vue'
import { Html5Qrcode } from 'html5-qrcode'

const result = ref('')
const error = ref('')

const onFileSelected = async (event) => {
  const file = event.target.files[0]
  if (!file) return

  const html5QrCode = new Html5Qrcode('reader')

  try {
    const scanResult = await html5QrCode.scanFile(file, true)
    result.value = scanResult
    error.value = ''
  } catch (err) {
    error.value = 'Failed to scan QR code from image'
    result.value = ''
  }
}
</script>

<template>
  <div class="file-scanner">
    <h3>Scan QR from Image File</h3>

    <input
      type="file"
      accept="image/*"
      @change="onFileSelected"
      class="file-input"
    />

    <div v-if="result" class="result">
      <h4>Result:</h4>
      <p>{{ result }}</p>
    </div>

    <div v-if="error" class="error">
      {{ error }}
    </div>
  </div>
</template>

<style scoped>
.file-scanner {
  max-width: 500px;
  margin: 0 auto;
  padding: 20px;
}

.file-input {
  width: 100%;
  padding: 10px;
  margin: 20px 0;
}

.result {
  margin-top: 20px;
  padding: 15px;
  background: #d4edda;
  border-radius: 8px;
}

.error {
  margin-top: 20px;
  padding: 15px;
  background: #f8d7da;
  border-radius: 8px;
  color: #721c24;
}
</style>

Composable for QR Operations

// composables/useQRCode.js
import { ref } from 'vue'

export function useQRCode() {
  const qrValue = ref('')
  const qrSize = ref(256)
  const qrLevel = ref('M')

  const downloadQR = () => {
    const canvas = document.querySelector('canvas')
    if (canvas) {
      const url = canvas.toDataURL('image/png')
      const link = document.createElement('a')
      link.download = 'qrcode.png'
      link.href = url
      link.click()
    }
  }

  const generateVCard = (name, phone, email) => {
    return `BEGIN:VCARD
VERSION:3.0
FN:${name}
TEL:${phone}
EMAIL:${email}
END:VCARD`
  }

  const generateWiFi = (ssid, password, encryption = 'WPA') => {
    return `WIFI:T:${encryption};S:${ssid};P:${password};;`
  }

  return {
    qrValue,
    qrSize,
    qrLevel,
    downloadQR,
    generateVCard,
    generateWiFi
  }
}

Using the Composable

<script setup>
import { useQRCode } from '@/composables/useQRCode'
import QrcodeVue from 'qrcode.vue3'

const { qrValue, qrSize, downloadQR } = useQRCode()
</script>

<template>
  <div>
    <input v-model="qrValue" placeholder="Enter URL" />

    <qrcode-vue
      :value="qrValue"
      :size="qrSize"
    />

    <button @click="downloadQR">Download</button>
  </div>
</template>

Scanner Composable

// composables/useQRScanner.js
import { ref, onUnmounted } from 'vue'
import { Html5QrcodeScanner } from 'html5-qrcode'

export function useQRScanner(elementId = 'qr-reader') {
  const scannedResult = ref('')
  const isScanning = ref(false)
  const error = ref('')
  let scanner = null

  const onScanSuccess = (decodedText) => {
    scannedResult.value = decodedText
    if (scanner) {
      scanner.clear()
      isScanning.value = false
    }
  }

  const onScanError = (err) => {
    console.warn('Scan error:', err)
  }

  const startScanning = () => {
    scanner = new Html5QrcodeScanner(
      elementId,
      {
        fps: 10,
        qrbox: { width: 250, height: 250 }
      },
      false
    )

    scanner.render(onScanSuccess, onScanError)
    isScanning.value = true
    error.value = ''
  }

  const stopScanning = () => {
    if (scanner) {
      scanner.clear()
      isScanning.value = false
    }
  }

  const restartScanning = () => {
    scannedResult.value = ''
    startScanning()
  }

  onUnmounted(() => {
    stopScanning()
  })

  return {
    scannedResult,
    isScanning,
    error,
    startScanning,
    stopScanning,
    restartScanning
  }
}

Using Scanner Composable

<script setup>
import { onMounted } from 'vue'
import { useQRScanner } from '@/composables/useQRScanner'

const {
  scannedResult,
  isScanning,
  startScanning,
  restartScanning
} = useQRScanner()

onMounted(() => {
  startScanning()
})
</script>

<template>
  <div>
    <div id="qr-reader"></div>

    <div v-if="scannedResult">
      <p>Result: {{ scannedResult }}</p>
      <button @click="restartScanning">Scan Again</button>
    </div>
  </div>
</template>

Multiple QR Codes

<!-- MultipleQR.vue -->
<script setup>
import { ref } from 'vue'
import QrcodeVue from 'qrcode.vue3'

const qrCodes = ref([
  { id: 1, text: 'https://example.com', color: '#000000' },
  { id: 2, text: 'https://github.com', color: '#000000' },
  { id: 3, text: 'https://vuejs.org', color: '#42b883' }
])

const addQR = () => {
  qrCodes.value.push({
    id: Date.now(),
    text: '',
    color: '#000000'
  })
}

const removeQR = (id) => {
  qrCodes.value = qrCodes.value.filter(qr => qr.id !== id)
}
</script>

<template>
  <div class="multiple-qr">
    <h3>Multiple QR Codes</h3>

    <div
      v-for="qr in qrCodes"
      :key="qr.id"
      class="qr-item"
    >
      <input
        v-model="qr.text"
        placeholder="Enter text"
        class="input"
      />

      <input
        v-model="qr.color"
        type="color"
        class="color-picker"
      />

      <qrcode-vue
        :value="qr.text || 'Empty'"
        :size="150"
        :color="{ dark: qr.color }"
      />

      <button @click="removeQR(qr.id)" class="remove-btn">
        Remove
      </button>
    </div>

    <button @click="addQR" class="add-btn">
      Add QR Code
    </button>
  </div>
</template>

<style scoped>
.multiple-qr {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.qr-item {
  display: flex;
  align-items: center;
  gap: 15px;
  margin: 15px 0;
  padding: 15px;
  border: 2px solid #ddd;
  border-radius: 8px;
}

.input {
  flex: 1;
  padding: 8px;
  border: 2px solid #ddd;
  border-radius: 4px;
}

.color-picker {
  width: 50px;
  height: 40px;
  border: none;
  cursor: pointer;
}

.remove-btn {
  padding: 8px 16px;
  background: #dc3545;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.add-btn {
  padding: 12px 24px;
  background: #42b883;
  color: white;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  margin-top: 20px;
}
</style>

Error Handling

<script setup>
import { ref } from 'vue'
import { Html5QrcodeScanner } from 'html5-qrcode'

const error = ref('')

const handlePermissionError = () => {
  error.value = 'Camera permission denied. Please enable camera access.'
}

const handleCameraError = (err) => {
  if (err.name === 'NotAllowedError') {
    handlePermissionError()
  } else if (err.name === 'NotFoundError') {
    error.value = 'No camera found on this device.'
  } else {
    error.value = `Error accessing camera: ${err.message}`
  }
}

const initScanner = async () => {
  try {
    const scanner = new Html5QrcodeScanner('reader', { fps: 10 }, false)
    scanner.render(onSuccess, onError)
  } catch (err) {
    handleCameraError(err)
  }
}
</script>

<template>
  <div>
    <div id="reader"></div>
    <div v-if="error" class="error-message">
      {{ error }}
    </div>
  </div>
</template>

Router Setup

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import QRManager from '@/components/QRManager.vue'

const routes = [
  {
    path: '/',
    redirect: '/qr'
  },
  {
    path: '/qr',
    name: 'QRManager',
    component: QRManager
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

Best Practices

  1. Error Correction Levels:

    • L: 7% damage tolerance
    • M: 15% (default)
    • Q: 25%
    • H: 30% (use with logos)
  2. QR Code Size: Minimum 200x200px for reliable scanning

  3. Contrast: Always use high contrast (dark on light)

  4. Camera Permissions: Handle permission errors gracefully

  5. Cleanup: Always clear scanner in onUnmounted

  6. Reactivity: Use computed for dynamic QR generation

  7. Performance: Use v-if instead of v-show for scanner component

Quick Reference

Generate QR:

<qrcode-vue
  :value="text"
  :size="256"
  level="M"
/>

Download QR:

const canvas = document.querySelector('canvas')
const url = canvas.toDataURL('image/png')

Start Scanner:

const scanner = new Html5QrcodeScanner('reader', { fps: 10 }, false)
scanner.render(onSuccess, onError)

Stop Scanner:

scanner.clear()

Special Formats:

  • URL: https://example.com
  • WiFi: WIFI:T:WPA;S:Name;P:Pass;;
  • Email: mailto:test@example.com
  • SMS: SMSTO:+1234567890:Message
  • vCard: BEGIN:VCARD\nVERSION:3.0\n...

Conclusion

Vue.js makes QR code generation and scanning simple with qrcode.vue3 and html5-qrcode. Both libraries integrate seamlessly with Vue’s reactivity system for dynamic QR code applications.

Gautam Sharma

About Gautam Sharma

Full-stack developer and tech blogger sharing coding tutorials and best practices

Related Articles

Angular

Generate and Scan QR Codes in Angular

Quick guide to generating and scanning QR codes in Angular browser applications. Simple examples with ngx-qrcode and html5-qrcode.

December 31, 2024
Vue

Vue.js Tutorial to Integrate jsPDF Library to Edit PDF in Browser

Complete guide to using jsPDF in Vue.js applications for creating and editing PDF documents directly in the browser.

December 28, 2024
JavaScript

How to Integrate Mozilla PDF.js in HTML: Build a PDF Viewer in Browser

Quick guide to integrating Mozilla PDF.js into your HTML application to build a functional PDF viewer directly in the browser.

December 31, 2024