-
SVG에 background-image 적용하기기타/메모장 2022. 7. 20. 16:44
내가 svg를 주로 사용하는 이유는 웹 상에서 특정 형태를 자유롭게 그려낼 수 있기 때문이다. 따라서 이런 svg로 만든 특정 형태 안에 background-image를 적용해보고 싶은 경우가 상당히 많았다. 하지만 svg에서는 css의 background-image 속성이 적용되지 않는다. 따라서 아예 다른 방식을 시도해보려 한다.
아래에서 시도한 방식들은 모두 React(CRA)에서 사용된 방식이다. (기본적인 Html이 아니다.)
1. Clip-path
svg를 이용해 Element를 생성하지 말고, 일반적인 div Element를 svg모양대로 잘라내는 설계도처럼 사용하는 방법이다.
clip-path (CSS)
clip-path 속성은 CSS에서 사용할 수 있는 속성이다. 해당 속성은 특정 범위를 지정하여, 범위 안의 부분을 제외한 바깥은 모두 잘라내는 특징을 가진다.위의 링크에서 보여주는 예시와 같이 보통 clip-path속성은 circle, polygon, path와 같은 방식으로 지정할 수 있다.
이때 path방식은 svg에서 사용할 수 있는 가장 강력한 방법이므로, svg를 적절히 조각할 수 있다면, 기본적인 Html태그로 만들어진 Element들도 svg처럼 조각할 수 있다는 뜻이다.
기본 path방식
다음은 기본적인 path방식을 이용해 img태그를 육각형의 모양으로 잘라내는 React Component 코드이다.
import IMG_JS from "../img/js.png"; export default function Hexagon(props) { const s = props.size; const w = s / (2 * Math.sqrt(3)); return ( <img src={IMG_JS} alt="js" width={4 * w} height={s} style={{ clipPath: `path('M${w} 0 L${3 * w} 0 L${4 * w} ${s / 2} L${ 3 * w } ${s} L${w} ${s} L0 ${s / 2} Z')`, }} /> ); }
img { object-fit: cover; }
이는 React의 다른 Component에서 <Hexagon size={200} />와 같은 형식으로 size를 줘서 사용할 수 있다.
다른 svg참조 방식
위에서 봤듯이 적용하고자 하는 태그에 적용한 css clip-path속성에 직접 path경로를 설정하는 방식도 있지만, 미리 svg를 이용해 만들어둔 경로를 참조하는 방식도 사용할 수 있다.
이를 위해 <defs>와 <clipPath>라는 태그를 이용한다.
<defs> 태그는 다음에 사용할 svg요소를 미리 저장하는 역할을 한다. 해당 태그 안에 쓰인 svg코드는 실제로 웹상에 그려지지 않지만, 다음에 해당 태그 내에 사용된 요소를 id를 이용해 참조할 수 있다.
<clipPath> 태그는 clipPath를 사용하기 위한 경로를 미리 지정하기 위한 태그로 사용된다. 이는 다음에 다른 요소에서 사용하기 위해 <defs> 태그 안에서 정의되어야 한다. 또한 참조를 위한 id를 반드시 설정해야 한다.
이를 이용해 미리 경로를 지정한 후, 이를 css의 clip-path속성에서 url을 이용해 사용할 수 있다.
정확한 사용법은 아래 코드와 같다.
import IMG_JS from "../img/js.png"; export default function Hexagon(props) { const s = props.size; const w = s / (2 * Math.sqrt(3)); return ( <div> <svg width="0" height="0"> <defs> <clipPath id="svg_hexagon"> <polygon points={`${w},0 ${3 * w},0 ${4 * w},${s / 2} ${ 3 * w },${s} ${w},${s} 0,${s / 2}`} /> </clipPath> </defs> </svg> <img src={IMG_JS} alt="js" width={4 * w} height={s} className="img" style={{ clipPath: "url(#svg_hexagon)", }} /> </div> ); }
img { object-fit: cover; }
이는 <clipPath> 태그에 id를 설정하고, 그 id를 사용할 html 태그의 css에서 clip-path속성을 url()과 함께 사용해 미리 작성한 svg 경로를 참조하는 형식이다.
위의 코드에서는 path대신 polygon을 사용해보았다. 방식은 조금 다르지만, 그 경로는 처음에 사용한 방식과 완전히 동일하다. 그저 평소 svg에서 자주 사용하는 태그를 자유롭게 사용하면 된다.
해당 방식은 아직 정확하게 숙지하지 못했지만, 지금까지 알고 있는 지식수준으로만 봤을 때는 별로 좋지 못한 방법인 것 같다. svg태그를 추가로 작성해야 하는 번거로움과 html구조가 복잡해진다는 단점 대비 이득이 딱히 보이지 않기 때문이다. 따라서 첫 번째 방식을 나는 선호하고 또 추천한다.
이후에 해당 방식이 가지는 이점을 발견하게 된다면, 여기에 덧붙여 적도록 하겠다.
2. Pattern
두 번째 방법으로는 아예 svg에 background-image를 설정하는 방식이 있다. 분명 처음에 svg에는 css의 background-image 속성이 적용되지 않는다고 했는데, 찾다 보니 방법이 있기는 있었다.
바로 <pattern> 태그와 <image> 태그를 이용하는 것이다.
먼저 <pattern> 태그는 svg의 배경으로 사용할 패턴을 만들어내는 태그이다. 해당 태그 내에 패턴으로 사용할 특정 형태를 svg를 이용해 만들고, 이를 나중에 다른 svg에서 참조해 배경으로 사용하는 형식이다. 작성하고 참조하는 방식은 이전에 사용한 clipPath와 굉장히 유사하다. 당연히 pattern 역시 이후에 참조하기 위한 용도이기 때문에 <defs> 태그 안에 작성해야 한다.
다음으로 <image> 태그는 <svg> 태그 안에서 이미지를 넣기 위해 사용하는 태그이다. 기본적으로 svg영역 안에서는 기존에 사용하던 <img> 태그를 사용할 수 없기 때문에, <image>라는 다소 특수한 태그를 사용한다.
우리가 pattern을 이용해 svg의 배경으로 사용하려 하는데, 해당 배경이 결국은 이미지여야 한다. 지금 우리는 svg에 background-image를 적용한 것처럼 작동하게 만드는 것이 목표이기 때문이다.
사용법은 간단하다. <image> 태그에서는 src속성 대신 href속성을 이용해 이미지를 가져온다. 이후 width와 height속성을 이용해 이미지의 크기를 지정해주면 끝이다.
이를 이용하여 작성한 코드는 다음과 같다.
import IMG_JS from "../img/js.png"; export default function Hexagon(props) { const s = props.size; const w = s / (2 * Math.sqrt(3)); return ( <svg width={4 * w} height={s}> <defs> <pattern id="js" patternUnits="userSpaceOnUse" width="360" height="360"> <image href={IMG_JS} width={4 * w} height={4 * w} /> </pattern> </defs> <polygon points={`${w},0 ${3 * w},0 ${4 * w},${s / 2} ${ 3 * w },${s} ${w},${s} 0,${s / 2}`} fill="url(#js)" /> </svg> ); }
해당 코드에 사용한 이미지의 크기가 360 × 360이라서, pattern의 width와 height는 360으로 지정했다. 이때 patternUnits 속성은 "userSpaceOnUse"로 지정했다. 정확히 어떤 역할을 하는지 이해하진 못했지만, 전체 svg 크기에 대해 상대적인 크기로 할지, 절대적인 크기로 할지를 정하는 것 같았다.
사실 상 아무것도 모르는 상태이므로, 아래의 mdn 문서를 직접 참고하기를 바란다.
이후 원래 표현하고자 하는 도형을 그리는 polygon에 fill 속성에서 미리 만들어두었던 pattern을 참조하였다.
참조 방식은 이전의 clipPath와 똑같은 방식이다. id를 이용해 참조할 요소를 정하면 된다.
다만, 해당 방식은 사용하지 않는 것이 좋다고 생각한다. 다시 한번 말하지만, 이는 해당 방식에 대한 이해도가 전혀 깊지 않은 상태에서 하는 말이다. 이후 pattern방식을 사용하는 것이 더 이득이라 생각하면, 추가로 글을 적겠다.
해당 방식을 사용하지 않는 것이 좋아 보이는 이유는 다음과 같다.
첫째로 pattern 방식을 이용해 image를 background로 사용하다 보니 정밀한 조절이 어려웠다. 기존 css의 background-image 속성의 경우, background-size, background-position, background-repeat 등을 이용해 이미지의 세밀한 조절이 가능했다. 하지만 pattern 방식의 경우 이미지가 패턴처럼 반복되지 않도록 이미지의 크기를 고려해서 값을 맞춰줘야 하는 번거로움이 있었다. 아직 이를 보완해주는 속성이나 방법을 찾지 못했기 때문에, 지금은 번거로운 방법이라 생각된다.
둘째로 이미지 자체가 깨끗하게 나오지 않는다. 이유는 아직 잘 모르겠다. 애초에 <pattern> 태그와 <image> 태그를 처음 써보았기 때문에, 해당 태그들이 가진 특성인지, 수치를 잘못 조절했는지는 알 수 없다. 하지만 분명 그 결과는 아래와 같이, clip-path 방식을 사용한 것에 비해 화질이 떨어지듯 보이는 것이 확실했다.
셋째로 svg로 만들어진 형태에 억지로 이미지를 집어넣는 방식보다, 이미지를 사용하기 위해 만들어진 <img> 태그를 svg형태대로 잘라내는 것이 더 타당하다는 생각이 들었기 때문이다. 이후에 이를 이용해 어떤 작업을 하든 간에 clip-path 방식이 원래 사용하기 위한 태그의 장점을 살리기 더 좋아 보인다. svg는 단순히 그래픽적인 요소일 뿐이지, 문서의 태그 역할을 하기에는 적절치 못하다는 생각이 든다.
따라서 결과적으로는 처음에 사용한 clip-path 방식을 앞으로 계속 사용해 나갈 생각이다. 해당 내용을 공부하면서, 처음에는 어떻게든 svg내에 이미지를 넣어보기 위해 노력했다. 하지만 자료를 찾을수록 '굳이 그래야 하나?'라는 생각이 들었다. 기존 태그를 여전히 잘 사용하면서, css와 svg를 적절히 조화시켜 형태만을 보기 좋게 꾸미면 될 일이었다.
참고
https://nyjchoi.tistory.com/17
https://css-tricks.com/clipping-masking-css/
https://itecnote.com/tecnote/html-fill-svg-path-element-with-a-background-image/