-
SVG 모션 Path 그리기Javascript 2020. 4. 18. 19:28
CSS 모션 경로를 사용하면 사용자 정의 사용자 정의 경로를 따라 요소에 애니메이션을 적용 할 수 있습니다.
이러한 경로는 SVG 경로와 동일한 구조를 따릅니다.
`offset-path`를 사용하여 요소의 경로를 정의합니다.
.block { offset-path: path('M20,20 C20,100 200,0 200,100'); }
위 요소는 상대적인 offset-path입니다. 하지만 여기서 문제점은 크기에 따라 반응하지 않는 다는 것입니다.
왜냐하면 해당 숫자들은 `px` 기반이기 때문이지요.
스테이지를 설정하기 위해 offset-distance 속성은 요소가 해당 경로에서 있어야하는 위치를 나타냅니다.
https://codepen.io/jh3y/pen/cd95c54d57891b095df99eaa02cdbd67
요소가 경로를 따라 거리를 정의 할 수있을뿐만 아니라 오프셋 회전을 사용하여 요소의 회전을 정의 할 수도 있습니다.
기본값은 auto이며, 요소가 경로를 따라갑니다.
.block { offset-path: path('M20,20 C20,100 200,0 200,100'); offset-distance: calc(var(--distance, 50) * 1%); }
https://codepen.io/jh3y/pen/d5aa1448cb745c1affe9fd6786958053
여기까지가 기본적인 static 환경에서의 path 그리기 입니다.
이제 어떻게 반응형 path를 그릴지 알아봅시다.
SVG는 포함 된 경로와 마찬가지로 뷰포트 크기로 확장됩니다.
반응형일 때 offset-path는 조정되지 않고 요소가 코스를 벗어납니다.
d3를 이용한 방법도 있습니다.
https://codepen.io/jh3y/pen/mdJMzWW?editors=0010
$color-1 = #ddd $color-2 = #222 $color-3 = rgba(128,191,255,0.5) $color-4 = #80bfff :root --size 75 * box-sizing border-box body display flex box-align center align-items center flex-direction column justify-content center min-height 100vh background $color-1 .container position relative height calc(var(--size) * 1vmin) width calc(var(--size) * 1vmin) border 2px solid $color-2 .element height 40px width 40px background $color-3 border 2px $color-4 solid position absolute top 0% left 0% offset-path path(var(--path)) animation travel 2s infinite alternate linear svg position absolute height calc(var(--size) * 1vmin) opacity 0.5 width calc(var(--size) * 1vmin) path fill none stroke-width 4px stroke #222 @media (prefers-color-scheme: dark) background $color-2 .container border 2px solid $color-1 svg path stroke #ddd @keyframes travel from offset-distance 0% to offset-distance 100%
import css from './style3.styl'; window.addEventListener('DOMContentLoaded', () => { // d3 library const { d3 } = window; const POINTS = [ [0, 0], [5, -5], [10, 0], [15, 5], [20, 0], ]; const CONTAINER = document.querySelector('.container'); const PADDING = 40; // assignPath const assignPath = () => { const { height: size } = CONTAINER.getBoundingClientRect(); // Create an X scale const xScale = d3 .scaleLinear() .domain([0, 20]) .range([PADDING, size - PADDING]); // Create an Y scale const yScale = d3 .scaleLinear() .domain([-5, 5]) .range([PADDING, size - PADDING]); // Map the POINTS using our scales const SCALED_POINTS = POINTS.map((POINT) => [xScale(POINT[0]), yScale(POINT[1])]); //Generate PATH string with our points const LINE = d3.line().curve(d3.curveBasis)(SCALED_POINTS); d3.select('svg').attr('viewBox', `0 0 ${size} ${size}`); d3.select('path').attr('d', `${LINE}`); // Assign path to animated element document.querySelector('.element').style.setProperty('--path', `"${LINE}"`); }; assignPath(); window.addEventListener('resize', assignPath); });
이것은 확실히 작동하지만 좌표 세트를 사용하여 SVG 경로를 선언하지 않을 것이므로 이상적이지 않습니다.
우리가하고 싶은 일은 벡터 드로잉 응용 프로그램에서 바로 경로를 가져 와서 최적화하는 것입니다.
그렇게하면 JavaScript 함수를 호출 할 수 있고 그렇게 하는 편이 최적의 드로잉 방법입니다.
자바스크립트를 이용하는 방법
이제 나머지를 처리하는 JavaScript 함수를 만들 수 있습니다.
이전에는 일련의 데이터 포인트를 가져 와서 확장 가능한 SVG 경로로 변환하는 함수를 만들었습니다.
그러나 이제 한 걸음 더 나아가 경로 문자열을 가져 와서 데이터 세트를 해결하려고합니다.
이러한 방식으로 사용자는 경로를 데이터 세트로 변환하려고 시도 할 필요가 없습니다.
이 함수에는 한 가지주의 사항이 있습니다.
경로 문자열 외에도 경로를 확장 할 수있는 경계가 필요합니다.
이러한 범위는 최적화 된 SVG에서 viewBox 속성의 세 번째 및 네 번째 값일 수 있습니다.
const path = "M10.362 18.996s-6.046 21.453 1.47 25.329c10.158 5.238 18.033-21.308 29.039-18.23 13.125 3.672 18.325 36.55 18.325 36.55l12.031-47.544"; const height = 79.375 // equivalent to viewbox y2 const width = 79.375 // equivalent to viewbox x2 const motionPath = new ResponsiveMotionPath({ height, width, path, });
1. path string을 dataset으로 변환합니다.
이를 가능하게하는 가장 큰 부분은 경로 세그먼트를 읽을 수 있다는 것입니다.
SVG GeometryElement API 덕분에 완전히 가능합니다.
경로가있는 SVG 요소를 생성하고 경로 속성을 d 속성에 할당하는 것으로 시작합니다.
// To convert the path data to points, we need an SVG path element. const svgContainer = document.createElement('div'); // To create one though, a quick way is to use innerHTML svgContainer.innerHTML = ` <svg xmlns="http://www.w3.org/2000/svg"> <path d="${path}" stroke-width="${strokeWidth}"/> </svg>`; const pathElement = svgContainer.querySelector('path');
그런 다음 해당 경로 요소에서 SVGGeometryElement API를 사용할 수 있습니다.
경로의 전체 길이에 대해 반복하고 경로의 각 길이에서 점을 반환하기만 하면됩니다.
convertPathToData = path => { // To convert the path data to points, we need an SVG path element. const svgContainer = document.createElement('div'); // To create one though, a quick way is to use innerHTML svgContainer.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg"> <path d="${path}"/> </svg>`; const pathElement = svgContainer.querySelector('path'); // Now to gather up the path points. const DATA = []; // Iterate over the total length of the path pushing the x and y into // a data set for d3 to handle 👍 for (let p = 0; p < pathElement.getTotalLength(); p++) { const { x, y } = pathElement.getPointAtLength(p); DATA.push([x, y]); } return DATA; }
2. 크기 비율을 만듭니다.
가장 큰 x 및 y 값을 가져 오는 기능과 viewBox와 관련된 비율을 계산하는 기능이 있습니다.
getMaximums = data => { const X_POINTS = data.map(point => point[0]) const Y_POINTS = data.map(point => point[1]) return [ Math.max(...X_POINTS), // x2 Math.max(...Y_POINTS), // y2 ] } getRatios = (maxs, width, height) => [maxs[0] / width, maxs[1] / height]
viewBox에 의해 정의 된 경계가 필요한 이유와 같습니다.
이는 컨테이너에 대한 모션 경로의 비율을 계산할 방법이 필요하기 때문입니다.
이 비율은 SVG viewBox에 대한 경로의 비율과 같습니다.
가장 큰 x 및 y 값을 가져 오는 기능과 viewBox와 관련된 비율을 계산하는 기능이 있습니다.
getMaximums = data => { const X_POINTS = data.map(point => point[0]) const Y_POINTS = data.map(point => point[1]) return [ Math.max(...X_POINTS), // x2 Math.max(...Y_POINTS), // y2 ] } getRatios = (maxs, width, height) => [maxs[0] / width, maxs[1] / height]
3. path를 그립시다
D3을 사용하여 앞서 생성 한 데이터 세트로 경로 문자열을 생성합니다.
d3.line()(data); // M10.362000465393066,18.996000289916992L10.107386589050293, etc.
D3의 멋진 점은 스케일을 만들 수 있다는 것입니다.
하나의 좌표 세트를 작성한 다음 D3가 경로를 다시 계산하도록 할 수 있습니다.
생성한 비율을 사용하여 컨테이너 크기를 기준으로이 작업을 수행 할 수 있습니다.
const xScale = d3 .scaleLinear() .domain([ 0, maxWidth, ]) .range([0, width * widthRatio]);
도메인은 0에서 가장 높은 x 값입니다.
대부분의 경우 범위는 0에서 컨테이너 너비에 너비 비율을 곱한 값입니다.
우리의 범위가 다를 수 있으며 시간을 조정해야 할 때가 있습니다.컨테이너의 가로 세로 비율이 경로의 가로 세로 비율과 일치하지 않는 경우입니다.
예를 들어, SVG에서 viewBox가 0 0 100 200 인 경로를 고려하십시오.
이는 종횡비가 1 : 2입니다.
그러나 높이와 너비가 20vmin 인 컨테이너에 이것을 그리면 컨테이너의 종횡비는 1 : 1입니다.
경로를 중앙에 유지하고 종횡비를 유지하려면 너비 범위를 채워야합니다.
이 경우 우리가 할 수있는 일은 경로가 컨테이너의 중심에 오도록 오프셋을 계산하는 것입니다.const widthRatio = (height - width) / height const widthOffset = (ratio * containerWidth) / 2 const xScale = d3 .scaleLinear() .domain([0, maxWidth]) .range([widthOffset, containerWidth * widthRatio - widthOffset])
두 개의 스케일이 있으면 스케일을 사용하여 데이터 포인트를 매핑하고 새 선을 생성 할 수 있습니다.
const SCALED_POINTS = data.map(POINT => [ xScale(POINT[0]), yScale(POINT[1]), ]); d3.line()(SCALED_POINTS); // Scaled path string that is scaled to our container
CSS 속성을 통해 인라인으로 전달하여 해당 경로를 요소에 적용 할 수 있습니다.
ELEMENT.style.setProperty('--path', `"${newPath}"`);
그런 다음 새로운 확장 경로를 생성하고 적용 할 시점을 결정하는 것은 우리의 책임입니다.
가능한 해결책은 다음과 같습니다.
const setPath = () => { const scaledPath = responsivePath.generatePath( CONTAINER.offsetWidth, CONTAINER.offsetHeight ) ELEMENT.style.setProperty('--path', `"${scaledPath}"`) } const SizeObserver = new ResizeObserver(setPath) SizeObserver.observe(CONTAINER)
https://codepen.io/jh3y/pen/dyoewER?editors=0010
최종본입니다.
출처 : https://css-tricks.com/create-a-responsive-css-motion-path-sure-we-can/
'Javascript' 카테고리의 다른 글
[JS] Javascript 스트링 메서드 (0) 2020.05.02 Async / Await 주의해서 다루기 (0) 2020.04.25 JS 이벤트 루프(callback, setTimeout, queue, 싱글스레드) (0) 2020.03.29 JavaScript에서 Priavate 구현 (0) 2020.03.15 [이미지] Lazy Loading에 대한 고찰 (0) 2020.02.15