Files
geovisio-website/src/components/sequence/WidgetOrientation.vue
2024-05-15 08:07:51 +00:00

302 lines
7.8 KiB
Vue

<template>
<div class="wrapper-widget-orientation">
<div
class="wrapper-img-road"
:style="{
transform: `rotate(${roadDegrees}deg)`
}"
></div>
<div class="wrapper-widget">
<div class="wrapper-car">
<div class="wrapper-elements">
<div class="rounded-transparent"></div>
<img
src="@/assets/images/car.svg"
alt=""
:style="{
transform: `rotate(${roadDegrees}deg)`
}"
class="car-img"
/>
<div
:style="{
transform: `rotate(${angle}deg)`
}"
id="rotateWrapper"
class="rotate-wrapper"
>
<div
id="rotate"
@mousedown="mousedown"
@mousemove="handleMouseMove"
@mouseup="mouseup"
@mouseout="mouseup"
class="cursor-img desktop"
>
<button class="arrow-img arrow-img-1" @click="clickAndMove(45)">
<img src="@/assets/images/icon/cursor-arrow.svg" alt="" />
</button>
<button class="arrow-img arrow-img-2" @click="clickAndMove(-45)">
<img src="@/assets/images/icon/cursor-arrow.svg" alt="" />
</button>
</div>
<div
id="rotate"
@touchstart="touchdown"
@touchmove="handleTouchMove"
@touchend="mouseup"
class="cursor-img responsive"
>
<button class="arrow-img arrow-img-1" @click="clickAndMove(45)">
<img src="@/assets/images/icon/cursor-arrow.svg" alt="" />
</button>
<button class="arrow-img arrow-img-2" @click="clickAndMove(-45)">
<img src="@/assets/images/icon/cursor-arrow.svg" alt="" />
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, watchEffect } from 'vue'
import { modulo180 } from '@/utils/calc'
let angleValue = ref<number>(0)
let angle = ref<number>(0)
let prevRotation = ref<number>(0)
let active = ref<boolean>(false)
let rotation = ref<number>(0)
let startAngle = ref<number>(0)
let rotateWrapper = ref<HTMLElement | null>(null)
let rotate = ref<HTMLElement | null>(null)
let center: { x: number; y: number } = { x: 0, y: 0 }
const R2D: number = 180 / Math.PI
const emit = defineEmits<{
(e: 'triggerAngle', value: number): void
(e: 'triggerMovingAngle', value: number): void
}>()
const props = defineProps({
roadDegrees: { type: Number, default: 0 },
seqBruteDeg: { type: Number, default: 0 }
})
watchEffect(() => {
angle.value = Math.round(props.seqBruteDeg)
})
onMounted(() => {
const app = document.getElementById('app')
rotateWrapper.value = document.getElementById('rotateWrapper')
rotate.value = document.getElementById('rotate')
if (app) app.addEventListener('mouseup', () => (active.value = false))
})
function touchdown(e: TouchEvent): void {
e.preventDefault()
if (!rotateWrapper.value) return
const bb = rotateWrapper.value.getBoundingClientRect()
const { top: t, left: l, height: h, width: w } = bb
center = { x: l + w / 2, y: t + h / 2 }
let x = e.changedTouches[0].clientX - center.x
let y = e.changedTouches[0].clientY - center.y
startAngle.value = R2D * Math.atan2(y, x)
active.value = true
}
function mousedown(e: MouseEvent): void {
e.preventDefault()
if (!rotateWrapper.value) return
const bb = rotateWrapper.value.getBoundingClientRect()
const { top: t, left: l, height: h, width: w } = bb
center = { x: l + w / 2, y: t + h / 2 }
let x = e.clientX - center.x
let y = e.clientY - center.y
startAngle.value = R2D * Math.atan2(y, x)
active.value = true
}
function handleTouchMove(e: TouchEvent): void {
if (!active.value || !rotateWrapper.value) return
const x = e.changedTouches[0].clientX - center.x
const y = e.changedTouches[0].clientY - center.y
const d = R2D * Math.atan2(y, x)
rotation.value = d - startAngle.value
const calc = angle.value + rotation.value
rotateWrapper.value.style.transform = `rotate(${calc}deg)`
}
function handleMouseMove(e: MouseEvent): void {
if (!active.value || !rotateWrapper.value) return
const x = e.clientX - center.x
const y = e.clientY - center.y
const d = R2D * Math.atan2(y, x)
rotation.value = d - startAngle.value
const calc = angle.value + rotation.value
rotateWrapper.value.style.transform = `rotate(${calc}deg)`
}
function mouseup(): void {
if (!active.value || !rotate.value) return
angle.value += Math.round(rotation.value)
if (rotation.value !== prevRotation.value) {
prevRotation.value = rotation.value
angleValue.value = angle.value
if (angleValue.value !== 0) {
emit('triggerMovingAngle', modulo180(angle.value, props.roadDegrees))
emit('triggerAngle', modulo180(angle.value, props.roadDegrees))
}
}
active.value = false
}
function clickAndMove(value: number): void {
const moduloAngle = modulo180(angle.value, Math.round(props.roadDegrees))
if (moduloAngle % 45 === 0) {
let angleToEmit = moduloAngle + value
if (angleToEmit > 180) angleToEmit = -135
if (angleToEmit < -180) angleToEmit = 135
return emit('triggerAngle', angleToEmit)
}
let closestMultiple = Math.ceil(moduloAngle / 45) * value
return emit('triggerAngle', closestMultiple)
}
</script>
<style scoped lang="scss">
.wrapper-widget-orientation {
position: relative;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
height: toRem(40);
width: 100%;
border-radius: toRem(1);
}
.wrapper-img-road {
background-image: url('@/assets/images/road.svg');
background-repeat: no-repeat;
background-size: contain;
background-position: center;
height: 210%;
width: 250%;
}
.wrapper-widget {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.wrapper-car {
height: toRem(40);
width: toRem(40);
display: flex;
justify-content: center;
position: relative;
}
.wrapper-elements {
width: 100%;
height: 100%;
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
.rotate-wrapper {
width: 100%;
height: 100%;
border-radius: 50%;
transform: rotate(0);
background-color: transparent;
display: flex;
justify-content: center;
position: relative;
z-index: 0;
.cursor-img {
background-image: url('@/assets/images/cursor.svg');
content: '';
display: block;
position: absolute;
left: toRem(2);
right: 0;
top: 0;
margin: auto;
height: 50%;
width: 74%;
background-repeat: no-repeat;
background-size: cover;
background-position: center;
cursor: move;
}
.desktop {
display: block;
}
.responsive {
display: none;
}
}
.arrow-img {
position: absolute;
border: none;
background-color: transparent;
height: toRem(5);
width: toRem(5);
padding: 0;
img {
height: 100%;
&:hover {
opacity: 0.8;
}
}
}
.arrow-img-1 {
right: toRem(4);
top: toRem(6);
}
.arrow-img-2 {
left: toRem(3.2);
bottom: toRem(10.5);
transform: rotate(90deg);
}
.car-img {
position: absolute;
height: 45%;
z-index: 1;
pointer-events: none;
}
.rounded-transparent {
background-color: rgba(white, 0.4);
height: toRem(25);
width: toRem(25);
position: absolute;
border-radius: 50%;
}
@media (max-width: toRem(76.8)) {
.rotate-wrapper {
.desktop {
display: none;
}
.responsive {
display: block;
}
}
.rotate-wrapper {
width: 75%;
height: 75%;
}
.arrow-img {
height: toRem(4);
width: toRem(4);
}
.arrow-img-1 {
right: toRem(3);
top: toRem(4.5);
}
.arrow-img-2 {
left: toRem(2.4);
bottom: toRem(7.88);
}
.car-img {
height: 40%;
}
}
</style>