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 |
댓글