border에 gradient color를 적용해보며 css mask에 대해 알아보자.
CSS Mask로 뭘 할 수 있는데??
css mask를 사용하면 특정 요소를 그릴 때 source element와 destination element를 가지고 mask 속성값이 따라 무엇을 어떤식으로 그릴지에 대한 것을 정할 수 있다.
위 이미지의 경계선과 같은 효과를 주는 방법을 알아보자.
<div className="bg-transparent w-8/12 h-20 position: fixed top-5 left-1/2 -translate-x-1/2 rounded-full border-4 border-transparent font-bold text-lg" ref={navRef}>
<div className="absolute inset-0 bg-gradient-to-r from-red-500 via-green-500 to-yellow-500 rounded-full color-morph-border p-[5px]"></div>
// 내부 내용
</div>
코드를 간단히 설명하면
1. 먼저 최상위 element를 만들어 준다.
2. 자식 요소를 absolute를 사용하여 부모와 같은 크기로 만들어준다. (Mask 속성이 적용될 gradation border )
우리는 일단 2번 자식요소만 보면된다.
CSS Mask 속성
자식요소를 보면 중간에 color-morph-border라는 class가 보인다. tailwind에서 사용하기 위해 사용자 정의한 class인데 이에 적용된 css는 다음과 같다.
.color-morph-border {
-webkit-mask: linear-gradient(#fff, #fff) content-box, linear-gradient(#fff, #fff);
-webkit-mask-composite: destination-out;
mask: linear-gradient(#fff, #fff) content-box, linear-gradient(#fff, #fff);
mask-composite: exclude;
}
간단하게 말하면 요소의 content-box와 border-box 간 mask 연산을 한다.
mask 옵션은 exclude를 사용하여 두 마스킹 영역 사이에 제외되는 영역을 실제로 그린다는 뜻이다.
mask의 속성을 좀 더 살펴보면 첫번째 인자로 linear-gradient을 사용한 것을 볼 수 있다.
브라우저 탭을 살펴보면 우리가 설정했던 mask값이 mask-clip, mask-image 속성에 들어간 것으로 보인다.
여기서 mask-image에 linear-gradient가 들어가있는 것을 확인할 수 있다. image로 사용해야 하기 때문에 단일색상값(#fff와 같은)을 사용하지 못하는 것으로 보인다.
CSS Mask 핵심
mask의 인자에 들어가는 mask-clip(content-box, default는 border-box.. etc)와 linear-gradient 이미지의 alpha 속성, mask-composition의 설정값(add, subtract, exclude, intersect)을 제대로 알고 있어야 다양한 상황에서 Mask를 이용해서 간단하게 CSS효과를 줄 수 있다.
위에서 사용한 설정값과 이미지를 다시보면
.mask-style {
mask: linear-gradient(#fff, #fff) content-box, linear-gradient(#fff, #fff);
mask-composite: exclude;
}
exclude를 사용했는데 이는 source와 destination의 두 영역이 겹치지 않는 부분으로 순서가 바뀌어도 결과가 동일했다.
.mask-style {
mask: linear-gradient(#fff, #fff), linear-gradient(#fff, #fff) content-box;
mask-composite: exclude;
}
subtract는 source에서 destination을 제외한 영역을 보여준다. 이를 사용해서 동일한 결과를 보여줄 수 있다.
.mask-style {
mask: linear-gradient(#fff, #fff), linear-gradient(#fff, #fff) content-box;
mask-composite: subtract;
}
.mask-style {
mask: linear-gradient(#fff, #fff) content-box, linear-gradient(#fff, #fff);
mask-composite: subtract;
}
결과 이미지는 동일하다. 그리고 content-box를 앞에하던 뒤에하던 똑같은 결과를 보여준다.
해당 속성은 mask-clip의 값을 결정하는데 border-box(기본값), padding-box, content-box을 설정할 수 있다.
브라우저에서 이 중 넓은 속성을 source로 지정하고 내부 속성을 destination으로 지정하는 듯하다. ( 아님 )
그리고 alpha를 사용해서 오른쪽으로 갈수록 alpha가 0이 되도록 해보겠다.
.mask-style {
mask: linear-gradient(to right, #fff, #fff0), linear-gradient(#fff, #fff) content-box;
mask-composite: subtract;
}
이 동작이 이해가 된다면 이제 mask를 자유롭게 다룰 수 있다.
이해가 안간다면 다음 그림을 보자.
어느 정도 이해가 갔으리라 생각된다.
마지막으로 mask 인자의 순서에 따른 동작을 살펴보자.
.mask-style {
mask: linear-gradient(#fff, #fff), linear-gradient(to right, #ffff, #fff0) content-box;
mask-composite: exclude;
}
.mask-style {
mask: linear-gradient(#fff, #fff), linear-gradient(to right, #ffff, #fff0) content-box;
mask-composite: subtract;
}
둘의 결과 이미지는 동일하다. 우리가 원하는 이미지다.
그런데 여기서 subtract일때 mask에 두 인자 위치를 바꿔보겠다.
.mask-style {
mask: linear-gradient(to right, #ffff, #fff0) content-box, linear-gradient(#fff, #fff);
mask-composite: subtract;
}
mask인자의 순서를 source와 destination에 맞게 설정해줘야 원하는 결과값을 얻을 수 있다는 것을 알 수 있다.
+ gradient color animation
<div className="bg-transparent w-8/12 h-20 position: fixed top-5 left-1/2 -translate-x-1/2 rounded-full border-4 border-transparent font-bold text-lg" ref={navRef}>
<div className="absolute inset-0 bg-gradient-to-r from-red-500 via-green-500 to-yellow-500 rounded-full color-morph-border p-[5px]"></div>
/* 내부 내용 */
</div>
useEffect(() => {
const element = document.querySelector(".color-morph-border");
let hue = 0;
const changeColor = () => {
if (!element) return;
hue += 1;
(element as HTMLElement).style.filter = `hue-rotate(${hue}deg)`;
if (hue >= 360) hue = 0;
requestAnimationFrame(changeColor);
};
changeColor();
}, []);
맨 처음에 나왔던 최상위 요소에 filter 속성을 적용해주면 그라데이션의 색상이 일정하게 변하는 것을 볼 수 있다.