feat(imagery): show viewable area when zoomed (#5877)

* feat: viewable area

* chore: add test

* fix: get image ref when real-time

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
This commit is contained in:
子瞻 Luci 2022-11-22 05:06:12 +08:00 committed by GitHub
parent a79646a915
commit 1ddf5e5137
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 136 additions and 1 deletions

View File

@ -31,21 +31,32 @@
:title="image.formattedTime"
>
<a
class="c-thumb__image-wrapper"
href=""
:download="image.imageDownloadName"
@click.prevent
>
<img
ref="img"
class="c-thumb__image"
:src="image.url"
fetchpriority="low"
@load="imageLoadCompleted"
>
</a>
<div
v-if="viewableArea"
class="c-thumb__viewable-area"
:style="viewableAreaStyle"
></div>
<div class="c-thumb__timestamp">{{ image.formattedTime }}</div>
</div>
</template>
<script>
const THUMB_PADDING = 4;
const BORDER_WIDTH = 2;
export default {
props: {
image: {
@ -63,6 +74,77 @@ export default {
realTime: {
type: Boolean,
required: true
},
viewableArea: {
type: Object,
default: function () {
return null;
}
}
},
data() {
return {
imgWidth: 0,
imgHeight: 0
};
},
computed: {
viewableAreaStyle() {
if (!this.viewableArea || !this.imgWidth || !this.imgHeight) {
return null;
}
const { widthRatio, heightRatio, xOffsetRatio, yOffsetRatio } = this.viewableArea;
const imgWidth = this.imgWidth;
const imgHeight = this.imgHeight;
let translateX = imgWidth * xOffsetRatio;
let translateY = imgHeight * yOffsetRatio;
let width = imgWidth * widthRatio;
let height = imgHeight * heightRatio;
if (translateX < 0) {
width += translateX;
translateX = 0;
}
if (translateX + width > imgWidth) {
width = imgWidth - translateX;
}
if (translateX + 2 * BORDER_WIDTH > imgWidth) {
translateX = imgWidth - 2 * BORDER_WIDTH;
}
if (translateY < 0) {
height += translateY;
translateY = 0;
}
if (translateY + height > imgHeight) {
height = imgHeight - translateY;
}
if (translateY + 2 * BORDER_WIDTH > imgHeight) {
translateY = imgHeight - 2 * BORDER_WIDTH;
}
return {
'transform': `translate(${translateX + THUMB_PADDING}px, ${translateY + THUMB_PADDING}px)`,
'width': `${width}px`,
'height': `${height}px`
};
}
},
methods: {
imageLoadCompleted() {
if (!this.$refs.img) {
return;
}
const { width: imgWidth, height: imgHeight } = this.$refs.img;
this.imgWidth = imgWidth;
this.imgHeight = imgHeight;
}
}
};

View File

@ -174,6 +174,7 @@
:active="focusedImageIndex === index"
:selected="focusedImageIndex === index && isPaused"
:real-time="!isFixed"
:viewable-area="focusedImageIndex === index ? viewableArea : null"
@click.native="thumbnailClicked(index)"
/>
</div>
@ -219,6 +220,8 @@ const ZOOM_SCALE_DEFAULT = 1;
const SHOW_THUMBS_THRESHOLD_HEIGHT = 200;
const SHOW_THUMBS_FULLSIZE_THRESHOLD_HEIGHT = 600;
const IMAGE_CONTAINER_BORDER_WIDTH = 1;
export default {
name: 'ImageryView',
components: {
@ -281,6 +284,8 @@ export default {
},
imageTranslateX: 0,
imageTranslateY: 0,
imageViewportWidth: 0,
imageViewportHeight: 0,
pan: undefined,
animateZoom: true,
imagePanned: false,
@ -516,6 +521,23 @@ export default {
}
return 'Alt drag to pan';
},
viewableArea() {
if (this.zoomFactor === 1) {
return null;
}
const imageWidth = this.sizedImageWidth * this.zoomFactor;
const imageHeight = this.sizedImageHeight * this.zoomFactor;
const xOffset = (imageWidth - this.imageViewportWidth) / 2;
const yOffset = (imageHeight - this.imageViewportHeight) / 2;
return {
widthRatio: this.imageViewportWidth / imageWidth,
heightRatio: this.imageViewportHeight / imageHeight,
xOffsetRatio: (xOffset - this.imageTranslateX * this.zoomFactor) / imageWidth,
yOffsetRatio: (yOffset - this.imageTranslateY * this.zoomFactor) / imageHeight
};
}
},
watch: {
@ -1063,12 +1085,12 @@ export default {
}
this.setSizedImageDimensions();
this.setImageViewport();
this.calculateViewHeight();
this.scrollToFocused();
},
setSizedImageDimensions() {
this.focusedImageNaturalAspectRatio = this.$refs.focusedImage.naturalWidth / this.$refs.focusedImage.naturalHeight;
if ((this.imageContainerWidth / this.imageContainerHeight) > this.focusedImageNaturalAspectRatio) {
// container is wider than image
this.sizedImageWidth = this.imageContainerHeight * this.focusedImageNaturalAspectRatio;
@ -1079,6 +1101,17 @@ export default {
this.sizedImageHeight = this.imageContainerWidth / this.focusedImageNaturalAspectRatio;
}
},
setImageViewport() {
if (this.imageContainerHeight > this.sizedImageHeight + IMAGE_CONTAINER_BORDER_WIDTH) {
// container is taller than wrapper
this.imageViewportWidth = this.sizedImageWidth;
this.imageViewportHeight = this.sizedImageHeight;
} else {
// container is wider than wrapper
this.imageViewportWidth = this.imageContainerWidth;
this.imageViewportHeight = this.imageContainerHeight;
}
},
handleThumbWindowResizeStart() {
if (!this.autoScroll) {
return;

View File

@ -285,6 +285,13 @@
flex: 0 0 auto;
padding: 2px 3px;
}
&__viewable-area {
position: absolute;
border: 2px yellow solid;
left: 0;
top: 0;
}
}
.is-small-thumbs {

View File

@ -529,6 +529,19 @@ describe("The Imagery View Layouts", () => {
done();
});
it('should display the viewable area when zoom factor is greater than 1', async () => {
await Vue.nextTick();
expect(parent.querySelectorAll('.c-thumb__viewable-area').length).toBe(0);
parent.querySelector('.t-btn-zoom-in').click();
await Vue.nextTick();
expect(parent.querySelectorAll('.c-thumb__viewable-area').length).toBe(1);
parent.querySelector('.t-btn-zoom-reset').click();
await Vue.nextTick();
expect(parent.querySelectorAll('.c-thumb__viewable-area').length).toBe(0);
});
it('should reset the brightness and contrast when clicking the reset button', async () => {
const viewInstance = imageryView._getInstance();
await Vue.nextTick();