Skip to content

Commit aebfb04

Browse files
committed
fix: messy layout calculation
Don't mess with layout calculation so much. Let CSS and Vue do more of the work. It's way to hard to reason which DOM elements are available at which point and have which size. Should fix some null pointer exceptions. See #58
1 parent b897d05 commit aebfb04

File tree

2 files changed

+75
-116
lines changed

2 files changed

+75
-116
lines changed

src/components/QrcodeReader.vue

Lines changed: 75 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,17 @@
1616
class="qrcode-reader__tracking-layer"
1717
></canvas>
1818

19-
<div class="qrcode-reader__camera-layer" ref="videoWrapper">
20-
<canvas ref="pauseFrame"></canvas>
21-
<video ref="video"></video>
22-
</div>
19+
<canvas
20+
ref="pauseFrame"
21+
v-show="!shouldScan"
22+
class="qrcode-reader__pause-frame"
23+
></canvas>
24+
25+
<video
26+
ref="video"
27+
v-show="shouldScan"
28+
class="qrcode-reader__camera"
29+
></video>
2330
</div>
2431
</div>
2532
</template>
@@ -65,10 +72,15 @@ export default {
6572
6673
computed: {
6774
68-
shouldScan () {
75+
shouldStream () {
6976
return this.paused === false &&
70-
this.cameraInstance !== null &&
71-
this.destroyed === false
77+
this.destroyed === false &&
78+
this.constraints.video !== false
79+
},
80+
81+
shouldScan () {
82+
return this.shouldStream === true &&
83+
this.cameraInstance !== null
7284
},
7385
7486
/**
@@ -148,22 +160,19 @@ export default {
148160
},
149161
150162
watch: {
151-
/**
152-
* Starts continuous scanning process as soon as conditions for that are
153-
* fullfilled. The process stops itself automatically when the conditions
154-
* are not fullfilled anymore.
155-
*/
156-
shouldScan (shouldScan) {
157-
if (shouldScan) {
158-
this.startScanning()
163+
164+
shouldStream (shouldStream) {
165+
if (!shouldStream) {
166+
const frame = this.cameraInstance.captureFrame()
167+
this.paintPauseFrame(frame)
159168
}
160169
},
161170
162-
paused (paused) {
163-
if (paused) {
164-
this.stopPlayback()
165-
} else {
166-
this.startPlayback()
171+
shouldScan (shouldScan) {
172+
if (shouldScan) {
173+
this.clearPauseFrame()
174+
this.clearTrackingLayer()
175+
this.startScanning()
167176
}
168177
},
169178
@@ -194,7 +203,6 @@ export default {
194203
this.cameraInstance = null
195204
} else {
196205
this.cameraInstance = await Camera(this.constraints, this.$refs.video)
197-
this.startPlayback()
198206
}
199207
},
200208
@@ -209,15 +217,18 @@ export default {
209217
210218
beforeResetCamera () {
211219
if (this.cameraInstance !== null) {
212-
this.stopPlayback()
213220
this.cameraInstance.stop()
214221
this.cameraInstance = null
215222
}
216223
},
217224
218225
onLocate (location) {
219226
if (this.trackRepaintFunction !== null) {
220-
this.repaintTrack(location)
227+
if (location === null) {
228+
this.clearTrackingLayer()
229+
} else {
230+
this.repaintTrackingLayer(location)
231+
}
221232
}
222233
},
223234
@@ -287,85 +298,57 @@ export default {
287298
return normalized
288299
},
289300
290-
repaintTrack (location) {
301+
repaintTrackingLayer (location) {
302+
const video = this.$refs.video
291303
const canvas = this.$refs.trackingLayer
292304
const ctx = canvas.getContext('2d')
293-
const cameraInstance = this.cameraInstance
294305
295-
window.requestAnimationFrame(() => {
296-
if (location === null) {
297-
ctx.clearRect(0, 0, canvas.width, canvas.height)
298-
} else {
299-
const displayWidth = cameraInstance.displayWidth
300-
const displayHeight = cameraInstance.displayHeight
306+
const displayWidth = video.offsetWidth
307+
const displayHeight = video.offsetWidth
308+
const resolutionWidth = video.videoWidth
309+
const resolutionHeight = video.videoHeight
301310
302-
canvas.width = displayWidth
303-
canvas.height = displayHeight
311+
window.requestAnimationFrame(() => {
312+
canvas.width = displayWidth
313+
canvas.height = displayHeight
304314
305-
const widthRatio = displayWidth / cameraInstance.resolutionWidth
306-
const heightRatio = displayHeight / cameraInstance.resolutionHeight
315+
const widthRatio = displayWidth / resolutionWidth
316+
const heightRatio = displayHeight / resolutionHeight
307317
308-
location = this.normalizeLocation(widthRatio, heightRatio, location)
318+
location = this.normalizeLocation(widthRatio, heightRatio, location)
309319
310-
this.trackRepaintFunction(location, ctx)
311-
}
320+
this.trackRepaintFunction(location, ctx)
312321
})
313322
},
314323
315-
startPlayback () {
316-
this.unlockCameraLayerSize()
317-
this.repaintTrack(null)
318-
319-
const pauseFrame = this.$refs.pauseFrame
320-
const ctx = pauseFrame.getContext('2d')
321-
322-
ctx.clearRect(0, 0, pauseFrame.width, pauseFrame.height)
323-
},
324-
325-
stopPlayback () {
326-
this.lockCameraLayerSize()
327-
328-
const pauseFrame = this.$refs.pauseFrame
329-
const ctx = pauseFrame.getContext('2d')
330-
const cameraInstance = this.cameraInstance
331-
332-
const displayWidth = cameraInstance.displayWidth
333-
const displayHeight = cameraInstance.displayHeight
334-
335-
pauseFrame.width = displayWidth
336-
pauseFrame.height = displayHeight
324+
clearTrackingLayer () {
325+
const canvas = this.$refs.trackingLayer
326+
const ctx = canvas.getContext('2d')
337327
338-
ctx.drawImage(this.$refs.video, 0, 0, displayWidth, displayHeight)
328+
window.requestAnimationFrame(() => {
329+
ctx.clearRect(0, 0, canvas.width, canvas.height)
330+
})
339331
},
340332
341-
/*
342-
* When a new stream is requested, the video element looses its width and
343-
* height, causing the component to collapse until the new stream is loaded.
344-
* Copying the size from the video element to its wrapper div compensates
345-
* for this effect.
346-
*/
347-
lockCameraLayerSize () {
348-
const videoWrapper = this.$refs.videoWrapper
349-
const cameraInstance = this.cameraInstance
333+
paintPauseFrame (imageData) {
334+
const canvas = this.$refs.pauseFrame
335+
const ctx = canvas.getContext('2d')
350336
351-
if (cameraInstance !== null) {
352-
const displayWidth = cameraInstance.displayWidth
353-
const displayHeight = cameraInstance.displayHeight
337+
window.requestAnimationFrame(() => {
338+
canvas.width = imageData.width
339+
canvas.height = imageData.height
354340
355-
videoWrapper.style.width = displayWidth + 'px'
356-
videoWrapper.style.height = displayHeight + 'px'
357-
}
341+
ctx.putImageData(imageData, 0, 0)
342+
})
358343
},
359344
360-
/**
361-
* The video elements wrapper div should not have a fixed size all the so
362-
* it can be responsive.
363-
*/
364-
unlockCameraLayerSize () {
365-
const videoWrapper = this.$refs.videoWrapper
345+
clearPauseFrame () {
346+
const canvas = this.$refs.pauseFrame
347+
const ctx = canvas.getContext('2d')
366348
367-
videoWrapper.style.width = ''
368-
videoWrapper.style.height = ''
349+
window.requestAnimationFrame(() => {
350+
ctx.clearRect(0, 0, canvas.width, canvas.height)
351+
})
369352
},
370353
371354
},
@@ -382,27 +365,11 @@ export default {
382365
383366
.qrcode-reader__inner-wrapper {
384367
position: relative;
385-
}
386-
387-
.qrcode-reader__camera-layer {
388-
position: relative;
389-
z-index: 10;
390-
}
391-
392-
.qrcode-reader__camera-layer > video {
393-
display: block;
394-
object-fit: contain;
395-
}
396-
397-
.qrcode-reader__inner-wrapper,
398-
.qrcode-reader__camera-layer,
399-
.qrcode-reader__camera-layer > video {
400368
max-width: 100%;
401369
max-height: 100%;
402370
}
403371
404372
.qrcode-reader__overlay,
405-
.qrcode-reader__camera-layer > canvas,
406373
.qrcode-reader__tracking-layer {
407374
position: absolute;
408375
width: 100%;
@@ -411,6 +378,14 @@ export default {
411378
left: 0;
412379
}
413380
381+
.qrcode-reader__camera,
382+
.qrcode-reader__pause-frame {
383+
display: block;
384+
object-fit: contain;
385+
max-width: 100%;
386+
max-height: 100%;
387+
}
388+
414389
.qrcode-reader__overlay {
415390
z-index: 30;
416391
}

src/misc/camera.js

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,6 @@ class Camera {
88
this.stream = stream
99
}
1010

11-
get resolutionWidth () {
12-
return this.videoEl.videoWidth
13-
}
14-
15-
get resolutionHeight () {
16-
return this.videoEl.videoHeight
17-
}
18-
19-
get displayWidth () {
20-
return this.videoEl.offsetWidth
21-
}
22-
23-
get displayHeight () {
24-
return this.videoEl.offsetHeight
25-
}
26-
2711
stop () {
2812
this.stream.getTracks().forEach(
2913
track => track.stop()

0 commit comments

Comments
 (0)