簡體   English   中英

使眼睛跟隨此 SVG 中的光標

[英]Make eyes follow cursor in this SVG

我正在嘗試使用本教程使 svg 的瞳孔跟隨光標:

https://dev.to/anomaly3108/make-svg-follow-cursor-using-css-and-js-2okp

我們有 4 個 div:

  1. eyeball_left
  2. eyeball_right
  3. pupil_left
  4. pupil_right

看起來 JS 正在工作,但角度不是很准確。 瞳孔太高,他們沒有保持在正確的位置。

 let eyeball_left = document.querySelector("#eyeball_left"), pupil_left = document.querySelector("#pupil_left"), eyeArea_left = eyeball_left.getBoundingClientRect(), pupil_leftArea = pupil_left.getBoundingClientRect(), R_left = eyeArea_left.width / 2, r_left = pupil_leftArea.width / 2, centerX_left = eyeArea_left.left + R_left, centerY_left = eyeArea_left.top + R_left; console.log(centerX_left) console.log(centerY_left) let eyeball_right = document.querySelector("#eyeball_right"), pupil_right = document.querySelector("#pupil_right"), eyeArea_right = eyeball_right.getBoundingClientRect(), pupil_rightArea = pupil_right.getBoundingClientRect(), R_right = eyeArea_right.width / 2, r_right = pupil_rightArea.width / 2, centerX_right = eyeArea_right.left + R_right, centerY_right = eyeArea_right.top + R_right; console.log(centerX_right) console.log(centerY_right) document.addEventListener("mousemove", (e) => { let x_left = e.clientX - centerX_left, y_left = e.clientY - centerY_left, theta_left = Math.atan2(y_left, x_left), angle_left = (theta_left * 180) / Math.PI + 360; let x_right = e.clientX - centerX_right, y_right = e.clientY - centerY_right, theta_right = Math.atan2(y_right, x_right), angle_right = (theta_right * 180) / Math.PI + 360; pupil_left.style.transform = `translateX(${ R_left - r_left + "px" }) rotate(${angle_left + "deg"})`; pupil_left.style.transformOrigin = `${r_left + "px"} center`; pupil_right.style.transform = `translateX(${ R_right - r_right + "px" }) rotate(${angle_right + "deg"})`; pupil_right.style.transformOrigin = `${r_right + "px"} center`; });
 #monster { height: 100px; width: 400px; }
 <div id="monster"> <svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="168.88 0 290.9 400.77"> <g> <title>Layer 1</title> <path id="svg_1" fill="#6c63ff" d="m296.30999,388.65991l0,-67.88825l-22,0l0,67.88825a13.98286,13.98286 0 0 0 -7,12.11175l36,0a13.98286,13.98286 0 0 0 -7,-12.11175z" /> <path id="svg_2" fill="#6c63ff" d="m355.30999,388.65991l0,-67.88825l-22,0l0,67.88825a13.98286,13.98286 0 0 0 -7,12.11175l36,0a13.98286,13.98286 0 0 0 -7,-12.11175z" /> <circle id="svg_3" fill="#6c63ff" r="145.45113" cy="238.54887" cx="314.33362" /> <ellipse id="svg_4" fill="#fff" ry="19.21053" rx="57.63158" cy="311.43609" cx="314.33362" /> <circle id="svg_5" fill="#fff" r="24.69925" cy="205.61654" cx="262.19076" /> <circle id="svg_6" fill="#fff" r="24.69925" cy="205.61654" cx="366.47648" /> {/* eyebol */} <circle id="eyeball_left" fill="#3f3d56" r="19.21053" cy="205.31579" cx="262.67948" /> <circle id="eyeball_right" fill="#3f3d56" r="19.21053" cy="205.31579" cx="366.73212" /> {/* eyebol */} <ellipse id="svg_9" fill="#3f3d56" ry="74.09774" rx="96.05263" cy="87.09774" cx="314.33362" /> <ellipse id="svg_10" fill="#3f3d56" ry="18" rx="38" cy="18" cx="314.33362" /> <path id="svg_11" fill="#3f3d56" d="m315.39428,259.75517c6.323,-6.40629 16.04713,-6.53419 24.2561,-4.42458c9.786,2.51489 18.116,8.57423 27.17791,12.79851a49.55555,49.55555 0 0 0 14.58024,4.54776a38.27945,38.27945 0 0 0 36.63871,-17.0858a38.7584,38.7584 0 0 0 4.54212,-30.91717a1.50128,1.50128 0 0 0 -2.89283,0.79752a35.70693,35.70693 0 0 1 -3.34417,27.11259a35.29669,35.29669 0 0 1 -35.30417,17.03843a49.62651,49.62651 0 0 1 -14.22886,-4.81212c-8.76148,-4.28973 -16.98465,-10.00419 -26.54935,-12.41745c-9.21411,-2.32481 -19.9481,-1.90083 -26.997,5.241c-1.35753,1.37543 0.76245,3.4981 2.12132,2.12132l-0.00002,-0.00001z" /> <path id="svg_12" fill="#3f3d56" d="m315.39428,257.63384c-6.22928,-6.31139 -15.3898,-7.36984 -23.77027,-5.92682c-9.6154,1.65567 -17.88675,6.88869 -26.379,11.36988c-8.6772,4.57879 -17.92825,8.08187 -27.8912,6.48578a35.20905,35.20905 0 0 1 -23.1751,-14.039a35.77653,35.77653 0 0 1 -5.208,-30.05228a1.50128,1.50128 0 0 0 -2.89283,-0.79752a38.80889,38.80889 0 0 0 2.82291,27.89016a37.47231,37.47231 0 0 0 20.97865,18.1838c9.41409,3.348 19.35061,2.63 28.52089,-1.11613c9.42621,-3.85066 17.77515,-10.13661 27.45644,-13.36827c8.93708,-2.98324 20.2603,-3.75844 27.41619,3.49176c1.3583,1.37619 3.47944,-0.7453 2.12132,-2.12132l0,-0.00004z" /> <circle id="svg_13" fill="#3f3d56" r="11" cy="258.5" cx="314.36371" /> {/* PUPIL */} <circle id="pupil_left" fill="#fff" r="4" cy="198.77165" cx="254.31" /> <circle id="pupil_right" fill="#fff" r="4" cy="198.77165" cx="376.31" /> {/* PUPIL */} </g> </svg>

這里的基本思想是,我使用線元素來決定眼睛的旋轉/方向。 一條線可以在兩端和中間有一個標記。 在這個例子中,眼球是一個標記,然后我根據鼠標的位置更新線的末端。

首先是一個帶輪廓的簡單示例,然后是完整示例:

 let l1 = document.querySelector("#l1"); let l2 = document.querySelector("#l2"); let svg1 = document.querySelector("#svg1"); const toSVGPoint = (svg, x, y) => { let p = new DOMPoint(x, y); return p.matrixTransform(svg.getScreenCTM().inverse()); }; document.addEventListener('mousemove', e => { let p = toSVGPoint(svg1, e.clientX, e.clientY); l1.setAttribute('x2', px); l1.setAttribute('y2', py); l2.setAttribute('x2', px); l2.setAttribute('y2', py); });
 <svg id="svg1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 50"> <circle cx="75" cy="25" r="20" fill="none" stroke="blue" /> <circle cx="125" cy="25" r="20" fill="none" stroke="blue" /> <line marker-start="url(#pupil)" id="l1" x1="75" y1="25" stroke="red" /> <line marker-start="url(#pupil)" id="l2" x1="125" y1="25" stroke="red" /> <defs> <marker id="pupil" viewBox="0 0 10 10" refX="16" refY="5" markerWidth="10" markerHeight="10" orient="auto-start-reverse"> <rect width="10" height="10" fill="none" stroke="red"/> <circle fill="none" stroke="green" r="4" cy="5" cx="5" /> </marker> </defs> </svg>

 let l1 = document.querySelector("#l1"); let l2 = document.querySelector("#l2"); let svg1 = document.querySelector("#svg1"); const toSVGPoint = (svg, x, y) => { let p = new DOMPoint(x, y); return p.matrixTransform(svg.getScreenCTM().inverse()); }; document.addEventListener('mousemove', e => { let p = toSVGPoint(svg1, e.clientX, e.clientY); l1.setAttribute('x2', px); l1.setAttribute('y2', py); l2.setAttribute('x2', px); l2.setAttribute('y2', py); });
 #monster { height: 100px; width: 400px; }
 <div id="monster"> <svg id="svg1" xmlns="http://www.w3.org/2000/svg" viewBox="168.88 0 290.9 400.77"> <g> <title>Layer 1</title> <path id="svg_1" fill="#6c63ff" d="m296.30999,388.65991l0,-67.88825l-22,0l0,67.88825a13.98286,13.98286 0 0 0 -7,12.11175l36,0a13.98286,13.98286 0 0 0 -7,-12.11175z" /> <path id="svg_2" fill="#6c63ff" d="m355.30999,388.65991l0,-67.88825l-22,0l0,67.88825a13.98286,13.98286 0 0 0 -7,12.11175l36,0a13.98286,13.98286 0 0 0 -7,-12.11175z" /> <circle id="svg_3" fill="#6c63ff" r="145.45113" cy="238.54887" cx="314.33362" /> <ellipse id="svg_4" fill="#fff" ry="19.21053" rx="57.63158" cy="311.43609" cx="314.33362" /> <circle id="svg_5" fill="#fff" r="24.69925" cy="205.61654" cx="262.19076" /> <circle id="svg_6" fill="#fff" r="24.69925" cy="205.61654" cx="366.47648" /> {/* eyebol */} <circle id="eyeball_left" fill="#3f3d56" r="19.21053" cy="205.31579" cx="262.67948" /> <circle id="eyeball_right" fill="#3f3d56" r="19.21053" cy="205.31579" cx="366.73212" /> {/* eyebol */} <ellipse id="svg_9" fill="#3f3d56" ry="74.09774" rx="96.05263" cy="87.09774" cx="314.33362" /> <ellipse id="svg_10" fill="#3f3d56" ry="18" rx="38" cy="18" cx="314.33362" /> <path id="svg_11" fill="#3f3d56" d="m315.39428,259.75517c6.323,-6.40629 16.04713,-6.53419 24.2561,-4.42458c9.786,2.51489 18.116,8.57423 27.17791,12.79851a49.55555,49.55555 0 0 0 14.58024,4.54776a38.27945,38.27945 0 0 0 36.63871,-17.0858a38.7584,38.7584 0 0 0 4.54212,-30.91717a1.50128,1.50128 0 0 0 -2.89283,0.79752a35.70693,35.70693 0 0 1 -3.34417,27.11259a35.29669,35.29669 0 0 1 -35.30417,17.03843a49.62651,49.62651 0 0 1 -14.22886,-4.81212c-8.76148,-4.28973 -16.98465,-10.00419 -26.54935,-12.41745c-9.21411,-2.32481 -19.9481,-1.90083 -26.997,5.241c-1.35753,1.37543 0.76245,3.4981 2.12132,2.12132l-0.00002,-0.00001z" /> <path id="svg_12" fill="#3f3d56" d="m315.39428,257.63384c-6.22928,-6.31139 -15.3898,-7.36984 -23.77027,-5.92682c-9.6154,1.65567 -17.88675,6.88869 -26.379,11.36988c-8.6772,4.57879 -17.92825,8.08187 -27.8912,6.48578a35.20905,35.20905 0 0 1 -23.1751,-14.039a35.77653,35.77653 0 0 1 -5.208,-30.05228a1.50128,1.50128 0 0 0 -2.89283,-0.79752a38.80889,38.80889 0 0 0 2.82291,27.89016a37.47231,37.47231 0 0 0 20.97865,18.1838c9.41409,3.348 19.35061,2.63 28.52089,-1.11613c9.42621,-3.85066 17.77515,-10.13661 27.45644,-13.36827c8.93708,-2.98324 20.2603,-3.75844 27.41619,3.49176c1.3583,1.37619 3.47944,-0.7453 2.12132,-2.12132l0,-0.00004z" /> <circle id="svg_13" fill="#3f3d56" r="11" cy="258.5" cx="314.36371" /> </g> {/* PUPIL */} <line marker-start="url(#pupil)" id="l1" x1="262.67948" y1="205.31579" stroke="none" /> <line marker-start="url(#pupil)" id="l2" x1="366.73212" y1="205.31579" stroke="none" /> {/* PUPIL */} <defs> <marker id="pupil" viewBox="0 0 10 10" refX="16" refY="5" markerWidth="10" markerHeight="10" orient="auto-start-reverse"> <circle fill="#fff" r="4" cy="5" cx="5" /> </marker> </defs> </svg> </div>

備選方案:更新<circle> cxcy屬性

這種方法需要計算

  1. 光標坐標與眼球中心之間的角度
  2. 圓上的新點位置(基於角度、半徑

演示示例

 const svg = document.getElementById('svg') document.addEventListener("mousemove", (e) => { movePupils(e); }); function movePupils(e) { let eyes = svg.querySelectorAll('.eye'); eyes.forEach(eye=>{ let eyeball = eye.querySelector('.eyeball'); let pupil = eye.querySelector('.pupil'); // get center cx/cy and radius let pCenter = {x: +eyeball.getAttribute('cx'), y:+eyeball.getAttribute('cy') }; let rEyeball = +eyeball.getAttribute('r'); let rPupil = +pupil.getAttribute('r'); // translate cursor HTML DOM coordinates to SVG DOM units let pCursor = new DOMPoint(e.clientX, e.clientY); pCursor = pCursor.matrixTransform(svg.getScreenCTM().inverse()); // get angle between cursor and eyeball center; let angle = (Math.atan2(pCursor.y - pCenter.y, pCursor.x - pCenter.x) * 180) / Math.PI; //get distance between cursor and eyeball center let a = pCursor.x - pCenter.x; let b = pCursor.y - pCenter.y; let distance = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)); // adjust pupil movement inside eyeball boundaries let offset = distance<rEyeball? 1/rEyeball*distance: 1; let radiusOuter = (rEyeball-rPupil)*offset; let pMoved = { x: pCenter.x + Math.cos((angle * Math.PI) / 180) * radiusOuter, y: pCenter.y + Math.sin((angle * Math.PI) / 180) * radiusOuter } // update attributes pupil.setAttribute('cx', pMoved.x) pupil.setAttribute('cy', pMoved.y) }) }
 body{ margin:5em; } svg{ width:20em; overflow:visible; border:1px solid #ccc; }
 <svg id="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 100"> <g class="eye"> <circle class="eyeball" cx="30" cy="50" r="25" fill="none" stroke="#000" /> <circle class="pupil" cx="30" cy="50" r="5" fill="#000" /> </g> <g class="eye"> <circle class="eyeball" cx="75" cy="25" r="20" fill="none" stroke="#000" /> <circle class="pupil" cx="75" cy="25" r="5" fill="#000" /> </g> <g class="eye"> <circle class="eyeball" cx="120" cy="50" r="25" fill="none" stroke="#000" /> <circle class="pupil" cx="120" cy="50" r="5" fill="#000" /> </g> <g class="eye"> <circle class="eyeball" cx="75" cy="75" r="20" fill="none" stroke="#000" /> <circle class="pupil" cx="75" cy="75" r="5" fill="#000" /> </g> </svg>

上面的腳本可以通過將所有眼球和瞳孔包裹在一個帶有“眼睛”類的組中來應用,如下所示:

  <g class="eye">
    <circle class="eyeball" cx="120" cy="50" r="25" />
    <circle class="pupil" cx="120" cy="50" r="5" />
  </g>

就像@chrwahl 的示例一樣,我們需要將 HTML DOM 坐標轉換為 SVG 用戶單位。

let pCursor = new DOMPoint(e.clientX, e.clientY);
pCursor = pCursor.matrixTransform(svg.getScreenCTM().inverse());

計算角度

let pCenter = {x: +eyeball.getAttribute('cx'), y:+eyeball.getAttribute('cy') };
let angle = (Math.atan2(pCursor.y - pCenter.y, pCursor.x - pCenter.x) * 180) / Math.PI;

微調眼球區域內的瞳孔定位

計算光標和眼球中心之間的距離,允許我們進一步調整瞳孔移動:如果光標在眼球內,瞳孔將以當前鼠標坐標為中心。

let a = pCursor.x - pCenter.x;
let b = pCursor.y - pCenter.y;
let distance =  Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
let offset = distance<rEyeball ? 1/rEyeball*distance : 1;
let radiusOuter = (rEyeball-rPupil)*offset;  

點在圓上

let pOnCircle = {
  x: pCenter.x + Math.cos((angle * Math.PI) / 180) * radiusOuter,
  y: pCenter.y + Math.sin((angle * Math.PI) / 180) * radiusOuter
}

因為我們希望將圓放置在眼球的邊界內,所以我們需要使用減小的半徑進行此計算(根據瞳孔的半徑)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM