Skip to content

Commit b8ccb2c

Browse files
committed
fix(QrcodeStream): cover entire space on fullscreen
Previously when rendered in fullscreen, the component was "contained" in the viewport. I.e. there was white space either below and above or left and right. Now, the viewport is "covered" instead. So if camera resolution ratio and display ratio don't match up, one side is cropped. Closes #83
1 parent 92c5a5f commit b8ccb2c

File tree

1 file changed

+57
-62
lines changed

1 file changed

+57
-62
lines changed

src/components/QrcodeStream.vue

Lines changed: 57 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,26 @@
11
<template lang="html">
22
<div class="wrapper">
3-
<div class="inside">
4-
<!--
5-
All DOM elements here are stacked upon each other.
6-
Order matters! The last element is on top.
7-
Therefore we don't need `z-index`.
8-
-->
9-
<video
10-
ref="video"
11-
v-show="shouldScan"
12-
class="camera"
13-
autoplay
14-
muted
15-
playsinline
16-
></video>
17-
18-
<canvas
19-
ref="pauseFrame"
20-
v-show="!shouldScan"
21-
class="pause-frame"
22-
></canvas>
23-
24-
<canvas ref="trackingLayer" class="tracking-layer"></canvas>
25-
26-
<div class="overlay">
27-
<slot></slot>
28-
</div>
3+
<!--
4+
Note that the order of DOM elements matters.
5+
It defines the stacking order.
6+
The first element is at the very bottom, the last element is on top.
7+
This eliminates the need for `z-index`.
8+
-->
9+
<video
10+
ref="video"
11+
v-show="shouldScan"
12+
class="camera"
13+
autoplay
14+
muted
15+
playsinline
16+
></video>
17+
18+
<canvas ref="pauseFrame" v-show="!shouldScan" class="pause-frame"></canvas>
19+
20+
<canvas ref="trackingLayer" class="tracking-layer"></canvas>
21+
22+
<div class="overlay">
23+
<slot></slot>
2924
</div>
3025
</div>
3126
</template>
@@ -212,44 +207,51 @@ export default {
212207
}
213208
},
214209
215-
/**
216-
* The coordinates are based on the original camera resolution but the
217-
* video element is responsive and scales with space available. Therefore
218-
* the coordinates are re-calculated to be relative to the video element.
219-
*/
220-
normalizeLocation(widthRatio, heightRatio, location) {
221-
const normalized = {};
222-
223-
for (const key in location) {
224-
normalized[key] = {
225-
x: Math.floor(location[key].x * widthRatio),
226-
y: Math.floor(location[key].y * heightRatio)
227-
};
228-
}
229-
230-
return normalized;
231-
},
232-
233210
repaintTrackingLayer(location) {
234211
const video = this.$refs.video;
235212
const canvas = this.$refs.trackingLayer;
236213
const ctx = canvas.getContext("2d");
237214
215+
// The visually occupied area of the video element.
216+
// Because the component is responsive and fills the available space,
217+
// this can be more or less than the actual resolution of the camera.
238218
const displayWidth = video.offsetWidth;
239219
const displayHeight = video.offsetHeight;
220+
221+
// The actual resolution of the camera.
222+
// These values are fixed no matter the screen size.
240223
const resolutionWidth = video.videoWidth;
241224
const resolutionHeight = video.videoHeight;
242225
226+
// Dimensions of the video element as if there would be no
227+
// object-fit: cover;
228+
// Thus, the ratio is the same as the cameras resolution but it's
229+
// scaled down to the size of the visually occupied area.
230+
const largerRatio = Math.max(
231+
displayWidth / resolutionWidth,
232+
displayHeight / resolutionHeight
233+
);
234+
const uncutWidth = resolutionWidth * largerRatio;
235+
const uncutHeight = resolutionHeight * largerRatio;
236+
237+
const xScalar = uncutWidth / resolutionWidth;
238+
const yScalar = uncutHeight / resolutionHeight;
239+
const xOffset = (displayWidth - uncutWidth) / 2;
240+
const yOffset = (displayHeight - uncutHeight) / 2;
241+
242+
const coordinatesAdjusted = {};
243+
for (const key in location) {
244+
coordinatesAdjusted[key] = {
245+
x: Math.floor(location[key].x * xScalar + xOffset),
246+
y: Math.floor(location[key].y * yScalar + yOffset)
247+
};
248+
}
249+
243250
window.requestAnimationFrame(() => {
244251
canvas.width = displayWidth;
245252
canvas.height = displayHeight;
246253
247-
const widthRatio = displayWidth / resolutionWidth;
248-
const heightRatio = displayHeight / resolutionHeight;
249-
250-
location = this.normalizeLocation(widthRatio, heightRatio, location);
251-
252-
this.trackRepaintFunction(location, ctx);
254+
this.trackRepaintFunction(coordinatesAdjusted, ctx);
253255
});
254256
},
255257
@@ -288,17 +290,10 @@ export default {
288290

289291
<style lang="css" scoped>
290292
.wrapper {
291-
display: flex;
292-
flex-flow: row nowrap;
293-
justify-content: space-around;
294-
align-items: center;
295-
}
296-
297-
.inside {
298293
position: relative;
299-
max-width: 100%;
300-
max-height: 100%;
301294
z-index: 0;
295+
width: 100%;
296+
height: 100%;
302297
}
303298
304299
.overlay, .tracking-layer {
@@ -311,8 +306,8 @@ export default {
311306
312307
.camera, .pause-frame {
313308
display: block;
314-
object-fit: contain;
315-
max-width: 100%;
316-
max-height: 100%;
309+
object-fit: cover;
310+
width: 100%;
311+
height: 100%;
317312
}
318313
</style>

0 commit comments

Comments
 (0)