Ionic Vue QR Code Scanner with Capacitor
In the previous article, we’ve given a brief demonstration on how to use the capacitor-plugin-dynamsoft-barcode-reader to create a cross-platform QR code scanner using Ionic React. In this article, we are going to use Ionic Vue to create such a QR code scanner.
Screenshots of the final results:
![]() |
![]() |
![]() |
---|---|---|
Home | Scanning | Home (with results) |
Getting started with Dynamsoft Barcode Reader
DOWNLOAD THE SDK WITH A 30-DAY LICENSE
REQUEST A 30-DAY LICENSE
Building an Ionic Vue QR Code Scanner
Let’s do this in steps.
New project
Create a new Ionic Vue app:
ionic start QRCodeScanner --type=vue --capacitor
We can start a server to have a live test in the browser:
ionic serve
To run it on Android:
ionic capacitor add android
ionic capacitor copy android // sync files
ionic capacitor run android
To run it on iOS:
ionic capacitor add ios
ionic capacitor copy ios // sync files
ionic capacitor open ios // use Xcode to open the project
Add camera permission
For iOS, add the following to ios\App\App\Info.plist
:
<key>NSCameraUsageDescription</key>
<string>For barcode scanning</string>
Install dependencies
npm install capacitor-plugin-dynamsoft-barcode-reader
Modify the home page and create a scanner page
In the home page, add a button to navigate to the scanner page, two checkboxes for configuring whether to scan QR codes only and whether to enable continuous scanning and display the barcode results if found.
Template of the home page:
<template>
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>QR Code Scanner</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-button expand="full" v-on:click="gotoScannerPage">Scan Barcodes</ion-button>
<ion-list>
<ion-item>
<ion-label>Continuous Scan</ion-label>
<ion-checkbox slot="end" v-model="sharedStates.continuousScan"></ion-checkbox>
</ion-item>
<ion-item>
<ion-label>Scan QR Codes Only</ion-label>
<ion-checkbox slot="end" v-model="sharedStates.QRCodeOnly"></ion-checkbox>
</ion-item>
<ion-list-header v-if="sharedStates.barcodeResults.length>0">
<ion-label>Results:</ion-label>
</ion-list-header>
<ion-item v-bind:key="'result'+index" v-for="(result,index) in sharedStates.barcodeResults">
<ion-label>{{result}}</ion-label>
</ion-item>
</ion-list>
</ion-content>
</ion-page>
</template>
Script of the home page:
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonButton, IonList, IonListHeader, IonLabel, IonCheckbox, IonItem, useIonRouter } from '@ionic/vue';
import { defineComponent } from 'vue';
import { states } from '../states'
export default defineComponent({
name: 'HomePage',
components: {
IonContent,
IonHeader,
IonPage,
IonTitle,
IonToolbar,
IonButton,
IonList,
IonListHeader,
IonCheckbox,
IonLabel,
IonItem
},
setup() {
const router = useIonRouter();
const sharedStates = states;
const gotoScannerPage = () => {
router.push('/scanner');
}
return {
sharedStates,
gotoScannerPage
};
},
});
Create a states.ts
file to share the states between the two pages.
export const states = reactive({
QRCodeOnly: false,
continuousScan: false,
barcodeResults: [] as any[],
});
Create a scanner page under this path: src\views\ScannerPage.vue
.
<template>
<ion-page></ion-page>
</template>
<script lang="ts">
import { IonPage, useIonRouter } from '@ionic/vue';
import { defineComponent, onMounted } from 'vue';
import { states } from '../states'
export default defineComponent({
name: 'HomePage',
components: {
IonPage
},
setup() {
const sharedStates = states;
const router = useIonRouter();
const goBack = () => {
sharedStates.barcodeResults = ["1","2","3"]; // dummy results to test the navigation
router.back();
}
onMounted(() => {
setTimeout(goBack,2000);
});
},
});
</script>
<style scoped>
</style>
Configure the router for the scanner page in src/router/index.ts
:
const routes: Array<RouteRecordRaw> = [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
name: 'Home',
component: HomePage
},
+ {
+ path: '/scanner',
+ name: 'Scanner',
+ component: ScannerPage
+ }
]
Create a QR code scanner component
-
Create a new component in the following path:
src\components\QRCodeScanner.vue
. -
Define props and emits:
const props = defineProps(['license','dceLicense','torchOn','runtimeSettings']); const emit = defineEmits(['onScanned','onPlayed']);
-
Initialize the plugin and start scanning in the
onMounted
event:<script lang="ts" setup> const initialized = ref(false); let frameReadListener:PluginListenerHandle|undefined; let onPlayedListener:PluginListenerHandle|undefined; onMounted(async () => { let options:Options = {}; if (props.license) { options.license = props.license; // license of Dynamsoft Barcode Reader } if (props.dceLicense) { options.dceLicense = props.dceLicense; // license of Dynamsoft Camera Enhancer } let result = await DBR.initialize(options); if (result.success === true) { initialized.value = true; frameReadListener = await DBR.addListener('onFrameRead', (scanResult:ScanResult) => { emit("onScanned",scanResult); }); onPlayedListener = await DBR.addListener("onPlayed", (result:{resolution:string}) => { emit("onPlayed",result.resolution); }); if (props.runtimeSettings) { console.log("update runtime settings"); await DBR.initRuntimeSettingsWithString({template:props.runtimeSettings}); } await DBR.startScan(); } }); </script>
-
Remove the listeners and stop scanning in the
onBeforeUnmount
event:onBeforeUnmount(() => { if (frameReadListener) { frameReadListener.remove(); } if (onPlayedListener) { onPlayedListener.remove(); } DBR.stopScan(); });
-
Watch the
torchOn
props for toggling the torch.watch(() => props.torchOn, (newVal, oldVal) => { if (newVal === true) { DBR.toggleTorch({on:true}); }else{ DBR.toggleTorch({on:false}); } });
Add the QR code scanner component to the scanner page
-
Add the QR code scanner component in the template:
<template> <ion-page> <QRCodeScanner license="DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==" :runtimeSettings="runtimeSettings" @onScanned="onScanned" @onPlayed="onPlayed" ></QRCodeScanner> </ion-page> </template>
-
In the script, define
onScanned
andonPlayed
functions. In addition, if it is set to scan QR codes only, update runtime settings to specify the barcode format to QR code, otherwise, decode all barcode formats.export default defineComponent({ name: 'HomePage', components: { IonPage, QRCodeScanner, }, setup() { const sharedStates = states; const runtimeSettings = ref(''); if (sharedStates.QRCodeOnly) { runtimeSettings.value = "{\"ImageParameter\":{\"BarcodeFormatIds\":[\"BF_QR_CODE\"],\"Description\":\"\",\"Name\":\"Settings\"},\"Version\":\"3.0\"}"; // update the runtime settings with a JSON template }else{ runtimeSettings.value = "{\"ImageParameter\":{\"BarcodeFormatIds\":[\"BF_ALL\"],\"Description\":\"\",\"Name\":\"Settings\"},\"Version\":\"3.0\"}"; } const router = useIonRouter(); let scanned = false; const onScanned = (result:ScanResult) => { if (result.results.length>0 && scanned === false && sharedStates.continuousScan === false) { scanned = true; sharedStates.barcodeResults = result.results; router.back(); // go to home page with barcode results } } const onPlayed = (resolution:string) => { console.log(resolution); } return { sharedStates, runtimeSettings, onScanned, onPlayed }; }, });
-
Update
states.ts
to use theTextResult
type.export const states = reactive({ QRCodeOnly: false, continuousScan: false, - barcodeResults: [] as any[], + barcodeResults: [] as TextResult[], });
-
Update the home page to use the
TextResult
typed result.<ion-item v-bind:key="'result'+index" v-for="(result,index) in sharedStates.barcodeResults"> - <ion-label> {{result}}</ion-label> + <ion-label> {{result.barcodeFormat+": "+result.barcodeText}}</ion-label> </ion-item>
Draw QR code overlays
We can use SVG to draw QR code overlays in continuous scanning mode.
-
Add the following in the template:
<svg :viewBox="viewBox" class="overlay" v-if="sharedStates.continuousScan" > <polygon v-bind:key="'polygon'+index" v-for="(barcodeResult,index) in barcodeResults" :points="getPointsData(barcodeResult)" class="barcode-polygon" /> <text v-bind:key="'text'+index" v-for="(barcodeResult,index) in barcodeResults" :x="barcodeResult.x1" :y="barcodeResult.y1" fill="red" font-size="25" > {{barcodeResult.barcodeText}}</text> </svg>
-
Add the following styles:
.barcode-polygon { fill:rgba(85,240,40,0.5); stroke:green; stroke-width:1; } .overlay { top: 0; left: 0; position: absolute; width: 100%; height: 100%; z-index: 998; }
-
In the script, define a
viewBox
property and abarcodeResults
property usingref
and update them in theonPlayed
andonScanned
events.const viewBox = ref("0 0 1280 720"); const barcodeResults = ref([] as TextResult[]); const onScanned = (result:ScanResult) => { if (result.results.length>0 && scanned === false && sharedStates.continuousScan === false) { scanned = true; sharedStates.barcodeResults = result.results; router.back(); }else{ viewBox.value = "0 0 " + frameWidth + " " + frameHeight; barcodeResults.value = result.results; } } const onPlayed = (resolution:string) => { const width = resolution.split("x")[0]; const height = resolution.split("x")[1]; frameWidth = parseInt(width); frameHeight = parseInt(height); } const getPointsData = (tr:TextResult) => { let pointsData = tr.x1 + "," + tr.y1 + " "; pointsData = pointsData + tr.x2+ "," + tr.y2 + " "; pointsData = pointsData + tr.x3+ "," + tr.y3 + " "; pointsData = pointsData + tr.x4+ "," + tr.y4; return pointsData; }
-
For Android and iOS platforms, we also need to handle the rotation. The default orientation of the image sensor is landscape, we need to rotate the localization results according to the device’s orientation and the frame’s orientation.
-
In
QRCodeScanner.vue
, rotate the points of the localization results.frameReadListener = await DBR.addListener('onFrameRead', (scanResult:ScanResult) => { for (let index = 0; index < scanResult.results.length; index++) { const result = scanResult.results[index]; if (scanResult.deviceOrientation && scanResult.frameOrientation) { handleRotation(result,scanResult.deviceOrientation,scanResult.frameOrientation); } } emit("onScanned",scanResult); }); const handleRotation = (result:any, orientation: string, rotation:number) => { let width,height; if (orientation == "portrait") { width = currentHeight; height = currentWidth; }else{ width = currentWidth; height = currentHeight; } for (let i = 1; i < 5; i++) { let x = result["x"+i]; let y = result["y"+i]; let rotatedX; let rotatedY; switch (rotation) { case 0: rotatedX = x; rotatedY = y; break; case 90: rotatedX = width - y; rotatedY = x; break; case 180: rotatedX = width - x; rotatedY = height - y; break; case 270: rotatedX = height - y; rotatedY = width - x; break; default: rotatedX = x; rotatedY = y; } result["x"+i] = rotatedX; result["y"+i] = rotatedY; } }
-
In
ScannerPage.vue
, switch frame width and frame height forviewBox
.if (result.deviceOrientation === "portrait") { viewBox.value = "0 0 " + frameHeight + " " + frameWidth; }else{ viewBox.value = "0 0 " + frameWidth + " " + frameHeight; }
-
Add floating action buttons to the scanner page
We can add floating action buttons to control the scanning.
Here, we add a button to stop scanning and a button to toggle the torch.
-
Add the following to
ScannerPage.vue
’s template:<ion-fab vertical="bottom" horizontal="start" slot="fixed"> <ion-fab-button> <ion-icon name="ellipsis-horizontal-outline"></ion-icon> </ion-fab-button> <ion-fab-list side="top"> <ion-fab-button @click="toggleFlash"> <ion-icon name="flashlight-outline"></ion-icon> </ion-fab-button> <ion-fab-button @click="close"> <ion-icon name="close-outline"></ion-icon> </ion-fab-button> </ion-fab-list> </ion-fab>
-
In the script, add icons, define relevant functions and properties:
setup() { addIcons({ 'ellipsis-horizontal-outline': ellipsisHorizontalOutline, 'flashlight-outline': flashlightOutline, 'close-outline': closeOutline, }); const torchOn = ref(false); const toggleFlash = () => { torchOn.value = !torchOn.value; } const close = () => { router.back(); } return { torchOn, toggleFlash, close, sharedStates, runtimeSettings, viewBox, barcodeResults, getPointsData, onScanned, onPlayed }; }
-
We also need to set the app root’s
z-index
to avoid the scanner UI appended tobody
blocking the app’s UI. This is for the web platform.Here is the modified part of
App.vue
:<template> <ion-app style="z-index:100"> <ion-router-outlet /> </ion-app> </template>
All right, we’ve now finished the Ionic Vue QR Code Scanner.
Source Code
Check out the source code to have a try on your own: