다노샵 상품 상세페이지에 Skeleton UI 적용하기

GTM ScrollDepthThreshold가 페이지 랜딩 초기부터 100을 기록하는 이슈를 해결하기 위한 UI 변경

FlyingSquirrel
8 min readSep 8, 2019

다노에서는 데이터 기반의 의사결정을 하기 위해 GA/GTM 등을 사용하고 있습니다.

다노에서는 기본적으로 데이터에 기반한 의사결정을 하고 있습니다. 데이터가 항상 절대적이진 않겠지만, 많은 부분에서 유의미하게 의사결정을 할 수 있는 참고자료가 되어주고 있는 것 같습니다. 그래서 A/B 테스트를 하기도 하고, Google Analytics(GA), Google Tag Manger(GTM) 같은 도구를 이용하여 데이터 트래킹을 하고 있습니다.

프론트엔드 개발자로서 개인적으로 생각하건대, 코드의 가독성이나 관리의 측면에서 본다면, 마케팅 대행사로 보내는 스크립트들이나 GA 이벤트를 보내는 코드(이하 마케팅 스크립트라고 할게요)들은 다소 지저분하게 보일 때도 있습니다.

실제 서비스를 구동할 때 마케팅 스크립트가 없어도 유저들이 사이트를 이용하는데는 지장이 없고, 우리가 데이터를 기반하여 의사결정을 하기 위해 필요한 것이기 때문입니다. 하지만 데이터 트래킹을 위해선 어딘가에는 필요한 이벤트를 보내는 내용을 집어넣어야 할텐데요, 다노에서는 실제 개발자가 관리하는 코드에서는 마케팅 스크립트는 최소화하면서도 마케팅 스크립트는 정상적으로 작동하게 하기 위해서 GTM 을 사용하고 있습니다.

GTM에서 Scroll Depth Thresdhold(스크롤 심도)라는 이름의 변수가 제공되는 것이 있습니다. 이 변수를 활용해서 트리거를 만들면, 웹페이지에서 가로 스크롤 또는 세로 스크롤이 전체 화면의 키(height)와 비교해서 10%,20%,30%…100%의 위치에 도달하면 내가 원하는 어떤 작업을 실행시키도록 할 수 있습니다. 10%,20%,.., 이런 값들도 custom하게 설정할 수 있습니다.

이번 포스팅에서는 Scroll Depth Thresdhold(스크롤 심도) 라는 것과 관련해서 디버깅해본 이야기와 skeleton ui를 적용한 내용을 기록해보고자 합니다.

ScrollDepthThreshold의 수치가 100을 찍어버리는 이슈 발생

상품 상세페이지로 이동할 때 Header 영역과 Footer 영역은 그대로 유지하고 path가 변경될 때마다 해당하는 contents를 다시 DOM에 뿌려주는 방식
다른 페이지에서 상품 상세페이지로 이동하는 순간을 포착! 내용물(contents)영역이 아직 없기 때문에 body의 height가 작은 상태입니다.

다노샵의 웹은 기본적으로 위처럼 header와 footer 영역을 유지하고 pathname이 변경됨에 따라 그 때마다 필요한 데이터를 contents로 rendering 하는 구조를 가지고 있습니다. 그래서 다른 페이지에서 상품 상세페이지에 랜딩(도착)하면 Scroll Depth Thresdhold 수치가 20, 30, 40, …으로 찍히다가 최종적으로 100을 찍는 (…!!) 이슈가 생깁니다. 😨 (헐 안돼..)

페이지가 이동하면서 서버에서 데이터를 받아서 rendering 준비를 할 동안 콘텐츠 영역의 키가 작아졌다 다시 커진다.
페이지가 이동하면서 서버에서 데이터를 받아서 rendering 준비를 할 동안 콘텐츠 영역의 키가 작아졌다 다시 커진다.

문제는 기본적으로 scrollDepthThreshold는 페이지당 한 번만 실행이 된다는 것입니다. 유저가 스크롤하는 위치를 데이터 수집하려고 했는데, 이미 페이지 랜딩을 하면서 100을 찍어버렸으니, 유저가 상세페이지를 볼 수 있을 시점에는 scrollDepthThreshold 이벤트가 호출되지 않습니다. 데이터 수집을 못한다는 이야기겠죠.

트리거는 페이지당 기준값마다 한 번만 실행됩니다. 사용자가 페이지 상단으로 다시 스크롤해도 페이지를 새로고침하거나 사용자가 동일한 트리거를 사용하는 새 페이지로 이동하지 않는 이상 트리거는 다시 실행되지 않습니다.
(출처: 스크롤 심도 트리거 - 구글 태그 관리자 고객센터 문서)
어디까지나 개인적인 생각이에요. 이 가설이 맞는지는 아직 확신이 없어요. 페이지를 이동하면서 전체 body의 높이가 낮아지는 순간 scrollDepthThreshold가 20, 30, 40, …, 100을 부르는 게 아닌가 생각했습니다.

어디까지 가설이지만, 페이지를 이동하면서 전체 body의 높이(offsetHeight)값이 줄어드는 순간에 scrollDepthThreshold 가 20, 30, 40, … , 100이라는 수치를 차례대로 호출하고 있는 것 같았습니다.

실제로도 google tag manager debugger를 이용해도 20, 30, 40, … , 100의 순서로 찍히는 걸 확인할 수 있었습니다.
실제로도 google tag manager debugger를 이용해도 20, 30, 40, … , 100의 순서로 찍히는 걸 확인할 수 있었습니다.
// 대략적인 컴포넌트 구조가 아래와 같습니다.<WebApp>
<Header />
<ProductDetail />
<Footer />
</WebApp>

React lifeCycle로 볼 때도 아마 이 가설이 맞지 않을까 싶었던 것은… 부모 컴포넌트인 <WebApp /> 에서는 pathname이 변경될 때마다 onRouteChanged() 함수가 호출는데, 이 함수 내부에서 scroll을 가장 위로 올려주고 있습니다(window.scrollTo(0, 0)). 자식인 <ProductDetail /> 이 랜딩될 때 호출되는 순서가 궁금해서 console.log를 찍어보았더니 아래와 같은 순서로 호출되는 것을 확인할 수 있었습니다.

1. <ProductDetail />의 componentDidMount() 호출
(body의 offsetHeight 작아짐)
(1과 2사이에 GTM에서 scrollDepthThreshold를 20,30,..100으로 계산하는 듯)
2. <WebApp />의 onRouteChanged() 호출되면서 scrollTo(0,0) 실행됨
(아직 서버에서 데이터 받아오고 있는 중, body의 offsetHeight 작은 상태)

3. 서버에서 받아온 데이터 rendering 되면서 body의 offsetHeight값이 다시 커짐

Height가 작아져서 생긴 이슈라면, 항상 고정된 키(height)를 부여하면 되지 않을까?

서버에서 데이터를 받아오는 사이에 contents영역의 height 값을 고정적으로 부여하면서 이슈를 해결했습니다.

그냥 고정적인 height를 부여하는 것보다는 이왕이면 비쥬얼적으로 예쁘게..! skeleton ui를 적용해보았습니다. css는 codePen을 참고했습니다(👉 Skeleton Screen With CSS)

gif 로 저장할 때 low화질로 저장되면서 그라데이션 준 값이 잘 안보이지만..! 실제로 보면 봐줄만해요.

고정된 height값과 스켈레톤 ui를 적용하면서 페이지 이동을 할 때 흐름이 끊기는 느낌이 덜하고 자연스럽게 이동하는 것 같이 느껴졌습니다.

GTM debugger 상에서도 20번의 Click이 상세페이지로 이동하는 클릭이벤트인데, 그 뒤에 scrollDepthThreshold이벤트가 fired되지 않은 것을 확인할 수 있었습니다. 👏👏👏👏👏

여전히 해결되지 않은 이슈😿

하지만 여전히 이슈가 있습니다. 다노샵은 React.js로 만든 SPA인데, SPA는 하나의 페이지이기 때문에, 이전 페이지(페이지라고 불렀지만 사실 pathname으로 페이지처럼 보이도록 구분한 것입니다 실제로는 다노샵은 싱글 페이지입니다)에서 만약 scrollDepthThreshold를 30을 찍은 후 상세페이지로 이동했다면, 상세페이지에서는 40이상이 되어야지만 scrollDepthThreshold 이벤트가 fired 됩니다. pathname이 바뀌었으니 scroll depth 측정이 0부터 시작해야하는데….. 말이죠.

pathname이 바뀔때마다 scrollDepthThreshold 를 초기화시켜주어야할텐데, GTM에서 어떻게 설정하는지 아직 찾지를 못했습니다. 뭐 방법이 있겠죠. 😖

--

--

FlyingSquirrel
FlyingSquirrel

Written by FlyingSquirrel

감성이 말랑말랑한 개발자입니다.

No responses yet