Svelte-Cubed: 여러 장치에서 액세스 가능하고 일관된 경험 만들기

이 기사는 svelte-cubed 및 three.js를 사용하여 3D 장면을 만드는 초보자 시리즈의 세 번째 기사입니다. 우리가 어떻게 여기까지 왔는지 알고 싶다면 처음부터 시작할 수 있습니다.
  • 파트 1:
  • 파트 2:

  • 이 짧은 기사에서 우리는 관련 없는 두 종류의 주제를 살펴볼 것입니다. 그러나 둘 다 사용자 경험 개선이라는 범주에 속합니다.
  • prefers-reduced-motion를 사용하여 장면에서 사물을 조건부로 애니메이션/전환합니다
  • .
  • threejs clock 및 getDelta()를 사용하여 장치 프레임 속도가 다른 사용자에 대해 동일한 동작을 렌더링함

  • 시작하기 위해 파트 2가 중단된 REPL은 다음과 같습니다.
    https://svelte.dev/repl/9b3b351fe187421b84a6f1616e2c9e3d

    조건부 모션: prefers-reduced-motion



    왜 중요합니까?

    Not everyone likes decorative animations or transitions, and some users outright experience motion sickness when faced with parallax scrolling, zooming effects, and so on. The user preference media query prefers-reduced-motion lets you design a motion-reduced variant of your site for users who have expressed this preference.



    - prefers-reduced-motion: Sometimes less movement is more의 Thomas Steiner( )

    그리고 우리는 모든 사람이 우리의 장면을 즐길 수 있기를 원하므로 먼저 Svelte 구성 요소에서 사용할 수 있도록 JavaScript에서 이 기본 설정을 감지하는 방법을 알아봅시다. 저는 그의 게시물A Svelte store for prefers-reduced-motion에서 설명한 Geoff Rich( )의 접근 방식을 사용할 것입니다.

    새 자바스크립트 파일stores.js에서 Geoff의 모든 코드를 훔쳐서(나중에 참조할 수 있도록 인용하세요!) 붙여넣을 수 있습니다.

    import { readable } from "svelte/store";
    
    /*
        Source: Geoff Rich, 
        "A Svelte store for prefers-reduced-motion", 
        URL: https://geoffrich.net/posts/svelte-prefers-reduced-motion-store/
    */
    const reducedMotionQuery = '(prefers-reduced-motion: reduce)';
    
    const getInitialMotionPreference = () => window.matchMedia(reducedMotionQuery).matches;
    
    export const reducedMotion = readable(getInitialMotionPreference(), set => {
      const updateMotionPreference = event => {
        set(event.matches);
      };
    
      const mediaQueryList = window.matchMedia(reducedMotionQuery);
      mediaQueryList.addEventListener('change', updateMotionPreference);
    
      return () => {
        mediaQueryList.removeEventListener('change', updateMotionPreference);
      };
    });
    
    

    reducedMotion 저장소는 값이 변경되면 애플리케이션의 어느 위치에서든 구독하고 반응할 수 있는 부울을 제공합니다. 어떻게 사용할 수 있습니까? 음, 우리가 애니메이션을 적용할 때마다 우선 기본 설정을 확인하고 필요에 따라 조정할 수 있습니다. 모션은 트위닝된 저장소와 SC.onFrame() 콜백의 두 소스에서 나옵니다.

    첫째: 사용자가 움직임 감소를 선호하는 경우 트위닝된 저장 기간은 0이 됩니다(즉, 값 a에서 값 b로 즉시 이동).

    import { reducedMotion } from "./stores"
    
    let scale = tweened(1, {duration: $reducedMotion ? 0 : 2000, easing: elasticOut});
    


    그게 다야? 그게 다야!

    두 번째: 사용자가 움직임 감소를 선호하지 않는 경우 모든 프레임에서 회전을 업데이트할 수 있습니다.

    if(!$reducedMotion){
      SC.onFrame(() => {
        rotate += .01;
      })
    }
    


    어떻게 테스트할 수 있습니까? Geoff는 다음을 다루었습니다.

    Here's how to simulate the setting in Chrome DevTools and where to enable the setting in various OSes and Firefox



    devtools의 설정만 시뮬레이션하고 있기 때문에 devtools를 열어두고 REPL을 새로 고쳐서 상황이 어떻게 변하는지 확인해야 합니다.



    장치 간 가변 프레임 속도 처리



    그 모든 단어는 무엇을 의미합니까? 훨씬 더 똑똑한 사람들이 나보다 더 잘 설명할 수 있지만, 한 번 시도한 다음 더 나은 설명을 알려 드리겠습니다.

    일부 컴퓨터/모니터 설정에는 강력한 그래픽이 있고 일부는 그렇지 않습니다. 예를 들어, 8면체는 각 프레임에서 0.1라디안으로 회전합니다. 따라서 초당 60프레임(fps)으로 실행되는 설정은 Octahedron이 초당 0.1라디안으로 60번 회전하는 것을 관찰하는 것입니다. 30fps로 실행되는 덜 강력한 설정은 Octahedron이 초당 0.1라디안으로 30번만 회전하는 것을 보는 것입니다. 그것들은 매우 다른 경험입니다!

    보다 정확하고 심층적인 설명은 Lewy Blue( )의 오픈 소스 책 Discover three.js에서 체크아웃The Animation Loop 장을 참조하십시오. three.js를 처음 사용하는 경우 시간을 내어 전체 책을 살펴보는 것이 좋습니다!

    기본적으로 현재 프레임 속도를 기반으로 하드 코딩된 값SC.onFrame을 표준화하는 방법이 필요합니다. threejs Clock과 메서드getDelta()를 사용하고 값에 델타를 곱하면 됩니다.

    const clock = new THREE.Clock();
    SC.onFrame(() => {
      rotate += .01 * clock.getDelta();
    })
    


    와우, 느립니다. 그러나 모두에게 느립니다! 이제 회전 값을 조정하여 원하는 속도를 얻을 수 있습니다(0.5 시도).

    다음 기사에서는 Octahedron 친구에서 벗어나 하나 이상의 glTF 모델을 장면에 로드하는 방법을 살펴보겠습니다!

    참조



    REPL: https://svelte.dev/repl/c301f0ac026d45bdbf4facf55b921d1f?version=3.48.0

    Octo.svelte




    <script>
        import * as THREE from "three";
        import * as SC from "svelte-cubed";
        import { tweened } from "svelte/motion"
        import { elasticOut } from "svelte/easing"
        import { reducedMotion } from "./stores"
    
        let scaleType = "MEDIUM";
        let scale = tweened(1, {duration: $reducedMotion ? 0 : 2000, easing: elasticOut});
    
        // reactive statement to update scale based on scaleType
        $: if (scaleType === "SMALL"){
            $scale = .25;
        } else if (scaleType === "MEDIUM"){
            $scale = 1;
        } else if (scaleType === "LARGE") {
            $scale = 1.75;
        }
    
        let rotate = 0;
        if(!$reducedMotion){
            const clock = new THREE.Clock();
            SC.onFrame(() => {
                rotate += 0.5 * clock.getDelta();
            })
        }
    </script>
    
    <SC.Canvas background={new THREE.Color('seagreen')}>
        <SC.AmbientLight
        color={new THREE.Color('white')}
        intensity={.5}
      />  
        <SC.DirectionalLight
        color={new THREE.Color('white')}
        intensity={.75}
        position={[10, 10, 10]}
      />
    
      <!-- MESHES -->
       <SC.Mesh
        geometry={new THREE.OctahedronGeometry()}
        material={new THREE.MeshStandardMaterial({
          color: new THREE.Color('salmon')
        })}
            rotation={[rotate, rotate, rotate]}
        scale={[$scale, $scale, $scale]}
      />
    
      <!-- CAMERA -->
      <SC.PerspectiveCamera near={1} far={100} fov={55}>
    
        </SC.PerspectiveCamera>
        <SC.OrbitControls />
    
    </SC.Canvas>
    
    <div class="controls">
        <label>
        SMALL
            <input type="radio" bind:group={scaleType} value="SMALL" />
        </label>
        <label>
        MEDIUM
            <input type="radio" bind:group={scaleType} value="MEDIUM" />
        </label>
        <label>
        LARGE
            <input type="radio" bind:group={scaleType} value="LARGE" />
        </label>
    </div>
    
    <style>
        .controls {
            position: absolute;
            top: .5rem;
            left: .5rem;
            background: #00000088;
            padding: .5rem;
            color: white;
        }
    </style>
    
    


    store.js




    import { readable } from "svelte/store"
    
    /*
        Source: Geoff Rich, 
        "A Svelte store for prefers-reduced-motion", 
        URL: https://geoffrich.net/posts/svelte-prefers-reduced-motion-store/
    */
    const reducedMotionQuery = '(prefers-reduced-motion: reduce)';
    
    const getInitialMotionPreference = () => window.matchMedia(reducedMotionQuery).matches;
    
    export const reducedMotion = readable(getInitialMotionPreference(), set => {
      const updateMotionPreference = event => {
        set(event.matches);
      };
    
      const mediaQueryList = window.matchMedia(reducedMotionQuery);
      mediaQueryList.addEventListener('change', updateMotionPreference);
    
      return () => {
        mediaQueryList.removeEventListener('change', updateMotionPreference);
      };
    });
    
    

    좋은 웹페이지 즐겨찾기