만만하게 봤다가 큰코다친 경험.
QR 마스크 페이지 만들기
이런 페이지 제작 요청을 받았다.
처음엔 간단한 구조에 "최소 기능 구현 20~30분" (+구동확인 및 디테일 보정 시간 30분)으로 예상하고 시작했다가
4시간 넘게 싸움🫠
[구현 조건]
1. 전체 영역에 검은 마스크(블러처리)가 있다.
2. 가운데 구멍이 뚫려있다.
3. 구멍 내부는 선명하게 보인다.
[실패한 접근법]
1. 가운데 정사각형 만든다.
2. 정사각형 내부 : 선명함 유지
3. 정사각형 외뷰 : 불투명 + 흐린 효과를 주고
CSS 신봉자는 2, 3을 생각할 땐 "CSS에 그런 속성이 있겠지~"하고 안일하게 생각함.
그러나 결과는 다음과 같았다.
2. 정사각형 내부 : 흐림 (선명해야함🤯)
3. 정사각형 외부 : 선명 (흐려야함🤯)
이유 : CSS 기본만으로는 "네모 부분만 투명하게 뚫기"가 안됨.
하위에 중첩된 요소에 속성을 "부여"하는 것은 쉽지만
하위에 중첩된 요소에 "제외"하는 것은 곤란했다.
CSS에서 영역의 여집합만 선택하는 것이 어려웠다.
ChatGPT와 사투를 벌여도 구멍 부분과 마스크 부분을 원하는 대로 뽑지 못했다.
+ 현재 구조에 적용하는 건 또다른 문제를 주었다.
[필요 개념]
▪️clip-path : "보이는 부분"만 남기는 것
▪️mask : "가리는 부분/비우는 부분"을 조절하는 것
1. mask + clip-path 활용 : 실패
clip-path : 요소를 "자르듯" 보이게 할 수 있는 CSS 속성 (ex: 네모, 원, 다각형, 커스텀 모양 다 가능)
// 실패 코드
{/* 오버레이 + 구멍 뚫기 */}
<div className="absolute inset-0 bg-black/80 backdrop-blur-sm">
{/* 클리핑 마스크 */}
<div className="absolute inset-0">
<div
className="mask-overlay"
style={{
WebkitMaskImage: `
radial-gradient(
circle at center,
transparent 0 7rem,
black 7.5rem
)
`,
maskImage: `
radial-gradient(
circle at center,
transparent 0 7rem,
black 7.5rem
)
`,
}}
></div>
</div>
</div>
{/* 오버레이 */}
<div className="absolute inset-0 flex flex-col items-center justify-center">
{/* 반투명 + 블러 배경 : bg-black/70 backdrop-blur-sm 이 부분이 핵심 */}
<div className="absolute inset-0 bg-black/70 backdrop-blur-sm" />
{/* 중앙 구멍 (네모) */}
<div className="relative w-[14.286rem] h-[14.286rem] rounded-[1.714rem] border-4 border-white z-10" />
</div>
</div>
그러나 여기서도 적용이 어려웠다.
진짜 구멍이 clip된게 아니라 "오버레이 위에 네모 div를 얹은 형태"라 내부 div에 포함되지 않은 영역이 속성을 부여하기 어려웠다.
.mask-scan {
-webkit-mask:
radial-gradient(farthest-side, transparent 0%, transparent 40%, black 41%, black 100%);
mask:
radial-gradient(farthest-side, transparent 0%, transparent 40%, black 41%, black 100%);
-webkit-mask-composite: xor;
mask-composite: exclude;
clip-path: inset(30% 20% 30% 20% round 1.5rem);
}
위 같이 -webkit-mask-composite: xor; mask-composite: exclude; 등 듣도보도 못한 속성을 사용해도 결과는 이랬다.
더 이상 디테일값을 조정해서 될 일이 아니라고 느꼈다.
[최종 방식]
1. SVG 마스크 사용 : 모서리 둥근 네모형 투명 구멍이 있는 그림을 만듦
"SVG로 불투명한 여백을 그려버리자"
(용어를 풀기 어렵네요. 미술 용어를 빌리자면 '선'이 아닌 '면치기'로 구현하는 것)
//QRCodeScannerOverlay 컴포넌트 구현
import { HTMLAttributes } from 'react'
interface QRCodeScannerOverlayProps extends HTMLAttributes<SVGElement> {
deviceWidth: number
deviceHeight: number
boxSize?: number
}
export const QRCodeScannerOverlay = ({
deviceWidth,
deviceHeight,
boxSize = 224,
...props
}: QRCodeScannerOverlayProps) => {
const boxHalf = boxSize / 2
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox={`0 0 ${deviceWidth} ${deviceHeight}`}
className="absolute inset-0 size-full object-cover"
preserveAspectRatio="xMidYMid meet"
{...props}>
<defs>
<mask
id="hole"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width={deviceWidth}
height={deviceHeight}>
<rect width="100%" height="100%" fill="white" />
<rect
x="50%"
y="50%"
width={boxSize}
height={boxSize}
rx="20"
ry="20"
fill="black"
transform={`translate(-${boxHalf}, -${boxHalf})`}
/>
</mask>
</defs>
<rect
width="100%"
height="100%"
fill="black"
fillOpacity="0.7"
mask="url(#hole)"
/>
<rect
x="50%"
y="50%"
width={boxSize}
height={boxSize}
rx="20"
ry="20"
stroke="white"
strokeWidth="4"
fill="none"
transform={`translate(-${boxHalf}, -${boxHalf})`}
/>
</svg>
)
}
여백의 width, height 값을 고정해버리거나 100%와 같은 값으로 주면,
마스크의 여백이 드러나거나 내부 사각형이 찌그러질 수 있으므로 flexible 하게 만들어 주기 위해 resize 이벤트리스너를 등록한다.
// 부모 또는 해당 컴포넌트에서 resize 이벤트 리스너 설정
useEffect(() => {
// 컴포넌트 마운트 시 스캔 시작
startScanning()
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
//사용
<QRCodeScannerOverlay
deviceWidth={deviceSize.width}
deviceHeight={deviceSize.height}
/>
'D.evelop > CSS' 카테고리의 다른 글
[CSS]권장 작성법 (0) | 2021.09.12 |
---|---|
[Flex]기존 css를 Flex속성으로 수정하기 (0) | 2021.09.06 |
[CSS]영역과 위치 잡기 - position편 (0) | 2021.09.01 |
[CSS]영역과 위치 잡기 - display편 (0) | 2021.08.31 |
댓글