본인인증, 계좌인증 등에서 화면에 타이머를 보여줄 때가 있습니다. 보통 setInterval()을 사용해 1초가 지나가면 숫자를 카운드 다운하고 해당 숫자를 화면에 표시하는 방법으로 구현합니다.
setInterval(func, delay, arg0, arg1, * … , argN)
여기서 setInterval()은 고정된 시간마다 함수를 반복적으로 호출하는 메서드입니다. 반환 값 interval ID(숫자)로 타이머를 식별할 때 사용합니다. clearInterval()에 전달하면 타이머를 제거할 수 있습니다.
- func : delay마다 실행되는 함수
- delay : 지연해야 하는 밀리초(1/1000)
- arg, ..., argN : fuc 함수로 전달되는 인수
** 비슷한 메서드로 setTimeout() 이 있는데 다른 점은 일정한 시간이 지난 후 한 번만 실행합니다. 반환 값은 timeout ID(숫자)로 타이머를 식별할 때 사용합니다. clearTimeout()에 전달하면 타이머를 제거할 수 있습니다.
** https://developer.mozilla.org/ko/docs/Web/API/Window/setTimeout
setTimeout() 전역 함수 - Web API | MDN
전역 setTimeout() 메서드는 만료된 후 함수나 지정한 코드 조각을 한 번 실행하는 타이머를 설정합니다.
developer.mozilla.org
export function useTimer(initSecond) {
const second = ref(initSecond)
let timerId = null
const setTimer = () => {
if (!timerId)
timerId = setInterval(() => {
second.value -= 1
}, 1000)
}
setTimer()
watch(second, (second) => {
if (second <= 0) {
clearInterval(timerId)
timerId = null
}
})
return { second }
}
<template>
{{ getTimeText(second) }}
</template>
<script>
import { useTimer } from '@/composables/timer'
export default {
setup() {
const { second } = useTimer(180)
return { second }
},
methods: {
getTimeText(totalSecond) {
const minute = parseInt(totalSecond / 60)
const second = totalSecond % 60
const minuteText = minute >= 10 ? minute : `0${minute}`
const secondText = second >= 10 ? second : `0${second}`
return `${minuteText}:${secondText}`
},
},
}
</script>
본인인증, 계좌인증 등 타이머를 재사용하기 위해 컴포저블로 구현했습니다. 이것만으로 충분할 수 있지만 만약 웹뷰에서 해당 타이머를 사용한다면 추가적으로 코드를 추가해야 합니다. IOS의 경우 백그라운드 모드가 되면 타이머가 동작하지 않습니다.
이 문제를 해결하기 위해서는 백그라운드 모드가 되는 순간과 다시 화면으로 돌아오는 순간 사이에 지나간 초를 빼야 합니다. 추가적으로 백그라운드 모드될 때 setInterval를 삭제하고, 다시 화면으로 돌아오는 순간 setInterval를 실행해야 합니다.
백그라운드 모드가 되는 순간과 다시 화면으로 돌아오는 순간을 알기 위해서는 Document.visibilityState을 활용하는 것이 좋습니다. Document.visibilityState는 document의 가시성을 반환하고 반환값이 visible일 때는 페이지가 적어도 부분적이라도 보였을 때이고, hidden일 때는 페이지가 사용자에게 보이지 않습니다. 실제로 document가 다른 탭이거나 OS 화면 잠금이 활성 상태일 때입니다.
** https://developer.mozilla.org/ko/docs/Web/API/Document/visibilityState
Document.visibilityState - Web API | MDN
Document.visibilityState 읽기 전용 property로, 이 element가 현재 표시된 컨텍스트를 나타내는 document의 가시성을 반환합니다. document가 background 또는 보이지 않는 탭(다른 탭)에 있는지, 또는 pre-rendering을
developer.mozilla.org
import { nextTick, ref, watch, onMounted, onUnmounted } from 'vue'
export function useEventListener(target, event, callback) {
onMounted(() => target.addEventListener(event, callback))
onUnmounted(() => target.removeEventListener(event, callback))
}
export function useTimer(initSecond, toggle) {
const second = ref(initSecond)
let beforeBackground = null
let timerId = null
const setTimer = () => {
if (!timerId)
timerId = setInterval(() => {
second.value -= 1
}, 1000)
}
setTimer()
watch(toggle, () => {
second.value = initSecond
})
watch(second, (newValue) => {
if (newValue <= 0) {
clearInterval(timerId)
timerId = null
}
})
useEventListener(window, 'visibilitychange', async () => {
if (second.value <= 0) return
if (document.visibilityState === 'hidden') {
clearInterval(timerId)
timerId = null
beforeBackground = new Date().getTime()
} else {
const interval = parseInt((new Date().getTime() - beforeBackground) * 0.001)
if (interval >= second.value) {
second.value = 0
return
}
second.value = interval >= second.value ? 0 : second.value - interval
await nextTick()
setTimer()
}
})
return { second }
}
<template>
{{ getTimeText(second) }}
</template>
<script>
import { ref } from 'vue'
import { useTimer } from '@/composables/timer'
export default {
setup() {
const toggle = ref(false)
const { second } = useTimer(180, toggle)
return { second, toggle }
},
methods: {
getTimeText(totalSecond) {
const minute = parseInt(totalSecond / 60)
const second = totalSecond % 60
const minuteText = minute >= 10 ? minute : `0${minute}`
const secondText = second >= 10 ? second : `0${second}`
return `${minuteText}:${secondText}`
},
},
}
</script>
** 재인증을 할 경우 다시 타이머를 돌려야 해서 toggle 기능도 추가했습니다. toggle의 값이 변경이 될 때, 값을 감지해 타이머를 재시작합니다.
** await nextTick이 왜 있는지 궁금하실 수 있는데 nextTick은 DOM 업데이트 발생을 기다리는 유틸리티입니다. 반응형 상태가 바로바로 DOM에 업데이트되지 않습니다. 그래서 백그라운드에 있었던 간격이 값에 반영된 다음 타이머를 시작하기 위해 사용했습니다.
'Dev > Vue.js' 카테고리의 다른 글
[Vue] 1. 스토리북에서 목킹하는 법 (router, provide/inject) (0) | 2025.01.18 |
---|---|
[Vue] Storybook 사용하는 법 (1) | 2025.01.15 |
[Vue] 이메일 유효성 검사 (0) | 2024.12.21 |
[Vue] 웹뷰에서 중요정보 노출 방지 (+ 메모리 덤프 확인하는 법) (0) | 2024.12.14 |
[Vue] 중복 클릭 방지 방법 (0) | 2024.12.07 |
댓글