2017-09-17 1 views
0

마우스 움직임 만 사용하여 JSON 장면 주위로 카메라를 이동하려고합니다. 그러나 카메라를 클릭하고 드래그하지 않고도 마우스의 움직임과 마우스 방향으로 움직이기를 원합니다.THREE.js 제한된 마우스가있는 카메라 이동

부분적으로 작동하는이 버전을 개발했지만 필요한 부분이 아닙니다. 나는 또한 카메라와 애니메이트 된 JSON 사이의 거리를 바꾸지 않는 것 외에도 카메라가 특정 지점에서 움직이지 않도록 찾고있다.

여기 내 코드입니다 :

<!DOCTYPE html> 
<html lang="en"> 
    <head> 
     <title>three.js webgl - animation - keyframes - json</title> 
     <meta charset="utf-8"> 
     <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> 
     <style> 

      body { 
       color: #fff; 
       font-family:Monospace; 
       font-size:13px; 
       text-align:center; 
       background-color: #fff; 
       margin: 0px; 
       overflow: hidden; 
      } 

      #info { 
       position: absolute; 
       top: 0px; width: 100%; 
       padding: 5px; 
      } 

      a { 
       color: #2983ff; 
      } 

     </style> 
    </head> 

    <body> 

     <div id="container"></div> 

     <div id="info"> 
      <a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - animation - keyframes - json 
     </div> 

     <script src="three.min.js"></script> 

     <script src="Detector.js"></script> 
     <script src="stats.min.js"></script> 

     <script> 

      var mouseX = 0, mouseY = 0; 
      var windowHalfX = window.innerWidth/2; 
      var windowHalfY = window.innerHeight/2; 


      var scene, camera, pointLight, stats; 
      var model; 
      var renderer, mixer, animationClip; 

      var clock = new THREE.Clock(); 
      var container = document.getElementById('container'); 

      stats = new Stats(); 
      container.appendChild(stats.dom); 

      renderer = new THREE.WebGLRenderer({ antialias: true }); 
      renderer.setPixelRatio(window.devicePixelRatio); 
      renderer.setSize(window.innerWidth, window.innerHeight); 
      container.appendChild(renderer.domElement); 

      document.addEventListener('mousemove', onDocumentMouseMove, false); 

      window.addEventListener('resize', onresize, false); 

      scene = new THREE.Scene(); 

      var grid = new THREE.GridHelper(20, 20, 0x888888, 0x888888); 
      grid.position.set(0, - 1.1, 0); 
      scene.add(grid); 

      camera = new THREE.PerspectiveCamera(70, window.innerWidth/window.innerHeight, 1, 1000); 
      camera.position.z = 30; 
      camera.position.y = 30; 
      // camera.position.x = 0; 
      // camera.position.set(- 5.00, 3.43, 11.31); 
      // camera.lookAt(new THREE.Vector3(- 1.22, 2.18, 4.58)); 

      scene.add(new THREE.AmbientLight(0x404040)); 

      pointLight = new THREE.PointLight(0xffffff, 1); 
      pointLight.position.copy(camera.position); 
      scene.add(pointLight); 

      new THREE.ObjectLoader().load('FuckIt2.json', function (model) { 

       scene.add(model); 

       mixer = new THREE.AnimationMixer(model); 
       mixer.clipAction(model.animations[ 0 ]).play(); 
       // 
       // model.position.y = 0; 
       // model.position.x = 0; 

       animate(); 

      }); 

      function onDocumentMouseMove(event) { 

       mouseY = (event.clientY - windowHalfX)/5; 
       mouseX = (event.clientX - windowHalfY)/5; 

      } 



      window.onresize = function() { 

       windowHalfX = window.innerWidth/1; 
       windowHalfY = window.innerHeight/1; 

       camera.aspect = window.innerWidth/window.innerHeight; 
       camera.updateProjectionMatrix(); 

       renderer.setSize(window.innerWidth, window.innerHeight); 

      }; 


      function animate() { 

       requestAnimationFrame(animate); 

       mixer.update(clock.getDelta()); 

       stats.update(); 

       render(); 
      } 

      function render() { 

       camera.position.x += (mouseX - camera.position.x) * 0.003; 
       camera.position.y += (- mouseY - camera.position.y) * 0.003; 
       camera.lookAt(scene.position); 
       renderer.render(scene, camera); 

} 
     </script> 

    </body> 
    </html> 

제가이 변경 무엇을 할 수 있는지 알려주세요! 모든 도움을 주시면 감사하겠습니다. 고맙습니다!

답변

0

왜 바퀴를 다시 발명합니까? 난 단지 TrackballControls.js 코드를 재사용하고 카메라를 회전하기 위해 마우스를 누르고 드래그하지 않고 이벤트 핸들링 동작을 수정하고 기본적으로 회전 모드를 활성화하지 않으려면 원하는대로 수정하십시오. 이 - 더러운 - codepen.

/** 
* @author Eberhard Graether/http://egraether.com/ 
* @author Mark Lundin/http://mark-lundin.com 
* @author Simone Manini/http://daron1337.github.io 
* @author Luca Antiga/http://lantiga.github.io 
*/ 

THREE.MyTrackballControls = function (object, domElement) { 

    var _this = this; 
    var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 }; 

    this.object = object; 
    this.domElement = (domElement !== undefined) ? domElement : document; 

    // API 

    this.enabled = true; 

    this.screen = { left: 0, top: 0, width: 0, height: 0 }; 

    this.rotateSpeed = 1.0; 
    this.zoomSpeed = 1.2; 
    this.panSpeed = 0.3; 

    this.noRotate = false; 
    this.noZoom = false; 
    this.noPan = false; 

    this.staticMoving = false; 
    this.dynamicDampingFactor = 0.2; 

    this.minDistance = 0; 
    this.maxDistance = Infinity; 

    this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ]; 

    // internals 

    this.target = new THREE.Vector3(); 

    var EPS = 0.000001; 

    var lastPosition = new THREE.Vector3(); 

    var _state = STATE.ROTATE, 
    _prevState = STATE.NONE, 


    _eye = new THREE.Vector3(), 

    _movePrev = new THREE.Vector2(), 
    _moveCurr = new THREE.Vector2(), 

    _lastAxis = new THREE.Vector3(), 
    _lastAngle = 0, 

    _zoomStart = new THREE.Vector2(), 
    _zoomEnd = new THREE.Vector2(), 

    _touchZoomDistanceStart = 0, 
    _touchZoomDistanceEnd = 0, 

    _panStart = new THREE.Vector2(), 
    _panEnd = new THREE.Vector2(); 

    // for reset 

    this.target0 = this.target.clone(); 
    this.position0 = this.object.position.clone(); 
    this.up0 = this.object.up.clone(); 

    // events 

    var changeEvent = { type: 'change' }; 
    var startEvent = { type: 'start' }; 
    var endEvent = { type: 'end' }; 


    // methods 

    this.handleResize = function() { 

    if (this.domElement === document) { 

     this.screen.left = 0; 
     this.screen.top = 0; 
     this.screen.width = window.innerWidth; 
     this.screen.height = window.innerHeight; 

    } else { 

     var box = this.domElement.getBoundingClientRect(); 
     // adjustments come from similar code in the jquery offset() function 
     var d = this.domElement.ownerDocument.documentElement; 
     this.screen.left = box.left + window.pageXOffset - d.clientLeft; 
     this.screen.top = box.top + window.pageYOffset - d.clientTop; 
     this.screen.width = box.width; 
     this.screen.height = box.height; 

    } 

    }; 

    this.handleEvent = function (event) { 

    if (typeof this[ event.type ] == 'function') { 

     this[ event.type ](event); 

    } 

    }; 

    var getMouseOnScreen = (function() { 

    var vector = new THREE.Vector2(); 

    return function getMouseOnScreen(pageX, pageY) { 

     vector.set(
     (pageX - _this.screen.left)/_this.screen.width, 
     (pageY - _this.screen.top)/_this.screen.height 
    ); 

     return vector; 

    }; 

    }()); 

    var getMouseOnCircle = (function() { 

    var vector = new THREE.Vector2(); 

    return function getMouseOnCircle(pageX, pageY) { 

     vector.set(
     ((pageX - _this.screen.width * 0.5 - _this.screen.left)/(_this.screen.width * 0.5)), 
     ((_this.screen.height + 2 * (_this.screen.top - pageY))/_this.screen.width) // screen.width intentional 
    ); 

     return vector; 

    }; 

    }()); 

    this.rotateCamera = (function() { 

    var axis = new THREE.Vector3(), 
     quaternion = new THREE.Quaternion(), 
     eyeDirection = new THREE.Vector3(), 
     objectUpDirection = new THREE.Vector3(), 
     objectSidewaysDirection = new THREE.Vector3(), 
     moveDirection = new THREE.Vector3(), 
     angle; 

    return function rotateCamera() { 

     moveDirection.set(_moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0); 
     angle = moveDirection.length(); 

     if (angle) { 

     _eye.copy(_this.object.position).sub(_this.target); 

     eyeDirection.copy(_eye).normalize(); 
     objectUpDirection.copy(_this.object.up).normalize(); 
     objectSidewaysDirection.crossVectors(objectUpDirection, eyeDirection).normalize(); 

     objectUpDirection.setLength(_moveCurr.y - _movePrev.y); 
     objectSidewaysDirection.setLength(_moveCurr.x - _movePrev.x); 

     moveDirection.copy(objectUpDirection.add(objectSidewaysDirection)); 

     axis.crossVectors(moveDirection, _eye).normalize(); 

     angle *= _this.rotateSpeed; 
     quaternion.setFromAxisAngle(axis, angle); 

     _eye.applyQuaternion(quaternion); 
     _this.object.up.applyQuaternion(quaternion); 

     _lastAxis.copy(axis); 
     _lastAngle = angle; 

     } else if (! _this.staticMoving && _lastAngle) { 

     _lastAngle *= Math.sqrt(1.0 - _this.dynamicDampingFactor); 
     _eye.copy(_this.object.position).sub(_this.target); 
     quaternion.setFromAxisAngle(_lastAxis, _lastAngle); 
     _eye.applyQuaternion(quaternion); 
     _this.object.up.applyQuaternion(quaternion); 

     } 

     _movePrev.copy(_moveCurr); 

    }; 

    }()); 


    this.zoomCamera = function() { 

    var factor; 

    if (_state === STATE.TOUCH_ZOOM_PAN) { 

     factor = _touchZoomDistanceStart/_touchZoomDistanceEnd; 
     _touchZoomDistanceStart = _touchZoomDistanceEnd; 
     _eye.multiplyScalar(factor); 

    } else { 

     factor = 1.0 + (_zoomEnd.y - _zoomStart.y) * _this.zoomSpeed; 

     if (factor !== 1.0 && factor > 0.0) { 

     _eye.multiplyScalar(factor); 

     } 

     if (_this.staticMoving) { 

     _zoomStart.copy(_zoomEnd); 

     } else { 

     _zoomStart.y += (_zoomEnd.y - _zoomStart.y) * this.dynamicDampingFactor; 

     } 

    } 

    }; 

    this.panCamera = (function() { 

    var mouseChange = new THREE.Vector2(), 
     objectUp = new THREE.Vector3(), 
     pan = new THREE.Vector3(); 

    return function panCamera() { 

     mouseChange.copy(_panEnd).sub(_panStart); 

     if (mouseChange.lengthSq()) { 

     mouseChange.multiplyScalar(_eye.length() * _this.panSpeed); 

     pan.copy(_eye).cross(_this.object.up).setLength(mouseChange.x); 
     pan.add(objectUp.copy(_this.object.up).setLength(mouseChange.y)); 

     _this.object.position.add(pan); 
     _this.target.add(pan); 

     if (_this.staticMoving) { 

      _panStart.copy(_panEnd); 

     } else { 

      _panStart.add(mouseChange.subVectors(_panEnd, _panStart).multiplyScalar(_this.dynamicDampingFactor)); 

     } 

     } 

    }; 

    }()); 

    this.checkDistances = function() { 

    if (! _this.noZoom || ! _this.noPan) { 

     if (_eye.lengthSq() > _this.maxDistance * _this.maxDistance) { 

     _this.object.position.addVectors(_this.target, _eye.setLength(_this.maxDistance)); 
     _zoomStart.copy(_zoomEnd); 

     } 

     if (_eye.lengthSq() < _this.minDistance * _this.minDistance) { 

     _this.object.position.addVectors(_this.target, _eye.setLength(_this.minDistance)); 
     _zoomStart.copy(_zoomEnd); 

     } 

    } 

    }; 

    this.update = function() { 

    _eye.subVectors(_this.object.position, _this.target); 

    if (! _this.noRotate) { 

     _this.rotateCamera(); 

    } 

    if (! _this.noZoom) { 

     _this.zoomCamera(); 

    } 

    if (! _this.noPan) { 

     _this.panCamera(); 

    } 

    _this.object.position.addVectors(_this.target, _eye); 

    _this.checkDistances(); 

    _this.object.lookAt(_this.target); 

    if (lastPosition.distanceToSquared(_this.object.position) > EPS) { 

     _this.dispatchEvent(changeEvent); 

     lastPosition.copy(_this.object.position); 

    } 

    }; 

    this.reset = function() { 

    _state = STATE.NONE; 
    _prevState = STATE.NONE; 

    _this.target.copy(_this.target0); 
    _this.object.position.copy(_this.position0); 
    _this.object.up.copy(_this.up0); 

    _eye.subVectors(_this.object.position, _this.target); 

    _this.object.lookAt(_this.target); 

    _this.dispatchEvent(changeEvent); 

    lastPosition.copy(_this.object.position); 

    }; 

    // listeners 

    function keydown(event) { 

    if (_this.enabled === false) return; 

    window.removeEventListener('keydown', keydown); 

    _prevState = _state; 

    if (_state !== STATE.NONE) { 

     return; 

    } else if (event.keyCode === _this.keys[ STATE.ROTATE ] && ! _this.noRotate) { 

     _state = STATE.ROTATE; 

    } else if (event.keyCode === _this.keys[ STATE.ZOOM ] && ! _this.noZoom) { 

     _state = STATE.ZOOM; 

    } else if (event.keyCode === _this.keys[ STATE.PAN ] && ! _this.noPan) { 

     _state = STATE.PAN; 

    } 

    } 

    function keyup(event) { 

    if (_this.enabled === false) return; 

    _state = _prevState; 

    window.addEventListener('keydown', keydown, false); 

    } 

    function mousedown(event) { 

    if (_this.enabled === false) return; 

    event.preventDefault(); 
    event.stopPropagation(); 

    if (_state === STATE.NONE) { 

     _state = event.button; 

    } 

    if (_state === STATE.ROTATE && ! _this.noRotate) { 

     _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY)); 
     _movePrev.copy(_moveCurr); 

    } else if (_state === STATE.ZOOM && ! _this.noZoom) { 

     _zoomStart.copy(getMouseOnScreen(event.pageX, event.pageY)); 
     _zoomEnd.copy(_zoomStart); 

    } else if (_state === STATE.PAN && ! _this.noPan) { 

     _panStart.copy(getMouseOnScreen(event.pageX, event.pageY)); 
     _panEnd.copy(_panStart); 

    } 

    //added by default 
    //document.addEventListener('mousemove', mousemove, false); 
    //document.addEventListener('mouseup', mouseup, false); 

    _this.dispatchEvent(startEvent); 

    } 

    function mousemove(event) { 



    if (_this.enabled === false) return; 

    event.preventDefault(); 
    event.stopPropagation(); 

    if (_state === STATE.ROTATE && ! _this.noRotate) { 

     _movePrev.copy(_moveCurr); 
     _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY)); 

    } else if (_state === STATE.ZOOM && ! _this.noZoom) { 

     _zoomEnd.copy(getMouseOnScreen(event.pageX, event.pageY)); 

    } else if (_state === STATE.PAN && ! _this.noPan) { 

     _panEnd.copy(getMouseOnScreen(event.pageX, event.pageY)); 

    } 

    } 

    function mouseup(event) { 

    if (_this.enabled === false) return; 

    event.preventDefault(); 
    event.stopPropagation(); 

    //_state = STATE.NONE; 

    //document.removeEventListener('mousemove', mousemove); 
    //document.removeEventListener('mouseup', mouseup); 
    _this.dispatchEvent(endEvent); 

    } 

    function mousewheel(event) { 

    if (_this.enabled === false) return; 

    event.preventDefault(); 
    event.stopPropagation(); 

    switch (event.deltaMode) { 

     case 2: 
     // Zoom in pages 
     _zoomStart.y -= event.deltaY * 0.025; 
     break; 

     case 1: 
     // Zoom in lines 
     _zoomStart.y -= event.deltaY * 0.01; 
     break; 

     default: 
     // undefined, 0, assume pixels 
     _zoomStart.y -= event.deltaY * 0.00025; 
     break; 

    } 

    _this.dispatchEvent(startEvent); 
    _this.dispatchEvent(endEvent); 

    } 

    function touchstart(event) { 

    if (_this.enabled === false) return; 

    switch (event.touches.length) { 

     case 1: 
     _state = STATE.TOUCH_ROTATE; 
     _moveCurr.copy(getMouseOnCircle(event.touches[ 0 ].pageX, event.touches[ 0 ].pageY)); 
     _movePrev.copy(_moveCurr); 
     break; 

     default: // 2 or more 
     _state = STATE.TOUCH_ZOOM_PAN; 
     var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 
     var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 
     _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt(dx * dx + dy * dy); 

     var x = (event.touches[ 0 ].pageX + event.touches[ 1 ].pageX)/2; 
     var y = (event.touches[ 0 ].pageY + event.touches[ 1 ].pageY)/2; 
     _panStart.copy(getMouseOnScreen(x, y)); 
     _panEnd.copy(_panStart); 
     break; 

    } 

    _this.dispatchEvent(startEvent); 

    } 

    function touchmove(event) { 

    if (_this.enabled === false) return; 

    event.preventDefault(); 
    event.stopPropagation(); 

    switch (event.touches.length) { 

     case 1: 
     _movePrev.copy(_moveCurr); 
     _moveCurr.copy(getMouseOnCircle(event.touches[ 0 ].pageX, event.touches[ 0 ].pageY)); 
     break; 

     default: // 2 or more 
     var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 
     var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 
     _touchZoomDistanceEnd = Math.sqrt(dx * dx + dy * dy); 

     var x = (event.touches[ 0 ].pageX + event.touches[ 1 ].pageX)/2; 
     var y = (event.touches[ 0 ].pageY + event.touches[ 1 ].pageY)/2; 
     _panEnd.copy(getMouseOnScreen(x, y)); 
     break; 

    } 

    } 

    function touchend(event) { 

    if (_this.enabled === false) return; 

    switch (event.touches.length) { 

     case 0: 
     _state = STATE.NONE; 
     break; 

     case 1: 
     _state = STATE.TOUCH_ROTATE; 
     _moveCurr.copy(getMouseOnCircle(event.touches[ 0 ].pageX, event.touches[ 0 ].pageY)); 
     _movePrev.copy(_moveCurr); 
     break; 

    } 

    _this.dispatchEvent(endEvent); 

    } 

    function contextmenu(event) { 

    if (_this.enabled === false) return; 

    event.preventDefault(); 

    } 

    this.dispose = function() { 

    this.domElement.removeEventListener('contextmenu', contextmenu, false); 
    this.domElement.removeEventListener('mousedown', mousedown, false); 
    this.domElement.removeEventListener('wheel', mousewheel, false); 

    this.domElement.removeEventListener('touchstart', touchstart, false); 
    this.domElement.removeEventListener('touchend', touchend, false); 
    this.domElement.removeEventListener('touchmove', touchmove, false); 

    document.removeEventListener('mousemove', mousemove, false); 
    document.removeEventListener('mouseup', mouseup, false); 

    window.removeEventListener('keydown', keydown, false); 
    window.removeEventListener('keyup', keyup, false); 

    }; 

    this.domElement.addEventListener('contextmenu', contextmenu, false); 
    this.domElement.addEventListener('mousedown', mousedown, false); 
    this.domElement.addEventListener('wheel', mousewheel, false); 

    this.domElement.addEventListener('touchstart', touchstart, false); 
    this.domElement.addEventListener('touchend', touchend, false); 
    this.domElement.addEventListener('touchmove', touchmove, false); 

    window.addEventListener('keydown', keydown, false); 
    window.addEventListener('keyup', keyup, false); 

    this.handleResize(); 

    // force an update at start 
    this.update(); 

    document.addEventListener('mousemove', mousemove, false); 
    document.addEventListener('mouseup', mouseup, false); 
}; 

THREE.MyTrackballControls.prototype = Object.create(THREE.EventDispatcher.prototype); 
THREE.MyTrackballControls.prototype.constructor = THREE.MyTrackballControls; 


if (! Detector.webgl) Detector.addGetWebGLMessage(); 

var container, stats; 

var camera, controls, scene, renderer; 

var cross; 

init(); 
animate(); 

function init() { 

    camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 1, 1000); 
    camera.position.z = 500; 

    controls = new THREE.MyTrackballControls(camera); 

    controls.rotateSpeed = 2.0; 
    controls.zoomSpeed = 1.2; 
    controls.panSpeed = 0.8; 


    controls.noPan = false; 

    controls.staticMoving = false; 
    controls.dynamicDampingFactor = 0.3; 

    controls.keys = [ 65, 83, 68 ]; 

    controls.addEventListener( 'change', render); 

    // world 

    scene = new THREE.Scene(); 
    scene.fog = new THREE.FogExp2(0xcccccc, 0.002); 

    var geometry = new THREE.CylinderGeometry(0, 10, 100, 16, 1); 
    var material = new THREE.MeshPhongMaterial({ color: 0xdddddd, specular: 0x009900, shininess: 30, shading: THREE.SmoothShading }); 



    for (var i = 0; i < 200; i ++) { 

    var mesh = new THREE.Mesh(geometry, material); 
    mesh.position.x = (Math.random() - 0.5) * 500; 
    mesh.position.y = (Math.random() - 0.5) * 500; 
    mesh.position.z = (Math.random() - 0.5) * 500; 
    mesh.updateMatrix(); 
    mesh.matrixAutoUpdate = false; 
    scene.add(mesh); 

    } 


    // lights 

    light = new THREE.DirectionalLight(0xffffff); 
    light.position.set(1, 1, 1); 
    scene.add(light); 

    light = new THREE.DirectionalLight(0x002288); 
    light.position.set(-1, -1, -1); 
    scene.add(light); 

    light = new THREE.AmbientLight(0x222222); 
    scene.add(light); 


    renderer = new THREE.WebGLRenderer({ antialias: false }); 
    renderer.setClearColor(scene.fog.color); 
    renderer.setPixelRatio(window.devicePixelRatio); 
    renderer.setSize(window.innerWidth, window.innerHeight); 

    container = document.getElementById('container'); 
    container.appendChild(renderer.domElement); 

    stats = new Stats(); 
    stats.domElement.style.position = 'absolute'; 
    stats.domElement.style.top = '0px'; 
    stats.domElement.style.zIndex = 100; 
    container.appendChild(stats.domElement); 

    // 

    window.addEventListener('resize', onWindowResize, false); 
    // 

    render(); 

} 

function onWindowResize() { 

    camera.aspect = window.innerWidth/window.innerHeight; 
    camera.updateProjectionMatrix(); 

    renderer.setSize(window.innerWidth, window.innerHeight); 

    controls.handleResize(); 

    render(); 

} 

function animate() { 

    requestAnimationFrame(animate); 
    controls.update(); 


} 

function render() { 

    renderer.render(scene, camera); 
    stats.update(); 
} 
+0

나는이 코드를 시도하고 나는 "에서 재산 '갱신'읽을 수 없습니다 : THREE.MyTrackballControls.render THREE.MyTrackballControls.dispatchEvent THREE.MyTrackballControls.update"라고 오류 메시지가 계속 –

+0

실제로 동작하는 라이브 예를 내가 만든 코드 핌 링크에 제공됩니다. 그 이상의 것이 어렵습니다 : https://codepen.io/leefsmp/pen/zEvaBK –