ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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

     

    CSS Motion Path offset-distance

    ...

    codepen.io

    요소가 경로를 따라 거리를 정의 할 수있을뿐만 아니라 오프셋 회전을 사용하여 요소의 회전을 정의 할 수도 있습니다. 

    기본값은 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

     

    CSS Motion Path offset-path + offset-rotate

    ...

    codepen.io

    여기까지가 기본적인 static 환경에서의 path 그리기 입니다.

     

    이제 어떻게 반응형 path를 그릴지 알아봅시다.

     

     

    SVG는 포함 된 경로와 마찬가지로 뷰포트 크기로 확장됩니다. 

    반응형일 때 offset-path는 조정되지 않고 요소가 코스를 벗어납니다.

     

    d3를 이용한 방법도 있습니다.

    https://codepen.io/jh3y/pen/mdJMzWW?editors=0010

     

    Responsive CSS Motion Path with d3.js 😎

    So we all love CSS Motion Path right? ❤️ But, it would be so much better if it was responsive 😅 So here's a solution using d3.js. Compose your path us...

    codepen.io

    $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

    최종본입니다.

     

     

    Drag and Drop Responsive Motion Path 🤓🎉

    Drag an SVG file onto the page containing a `path` and have that `path` scale with the container size 👍 Proof of concept where the JavaScript `class` ...

    codepen.io

     

     

    출처 : https://css-tricks.com/create-a-responsive-css-motion-path-sure-we-can/

Designed by Tistory.