RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1602329
Accepted
Alexandr_TT
Alexandr_TT
Asked:2024-12-13 15:27:45 +0000 UTC2024-12-13 15:27:45 +0000 UTC 2024-12-13 15:27:45 +0000 UTC

如何制作军刀凸轮机构的动画?

  • 772

祝贺比赛获胜者 Stanislav Volodarskiy!

他的解决方案很有趣、原创且非常专业。
他们100%满足比赛条件。


除了他的解决方案之外,还发布了几个非常可靠的答案。给他们打高分是公平的,但一场比赛只能产生一名获胜者。
因此,我呼吁潜在的赞助商:请分享你的萝卜并重新启动,这里有一个新的比赛。

我有一张军刀凸轮机构的照片。

在此输入图像描述

有*.gif 机构动画

在此输入图像描述

我为凸轮机构创建了一个 svg 代码:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400" viewBox="0 0 500 500">
                                                                          
     <!-- Шарнир -->
<path fill="grey" stroke="black" stroke-width="3" d="M344.3 197.6a49.6 49.6 0 0 1 34.4 13A57 57 0 0 1 396 251a54 54 0 0 1-17.4 36.8c-9 8.2-22 13.1-34.2 13a54.4 54.4 0 0 1-36.8-15.4A51.4 51.4 0 0 1 294 251a55.4 55.4 0 0 1 13.7-37.6 53.7 53.7 0 0 1 36.6-16z"/>
            <!-- Вертикальный стержень -->
<path fill="grey" stroke="black" stroke-width="3" d="M387.3 13.1h30v440.5h-30z"/>
                <!-- Ось кулачка -->
<path  fill="#626262" stroke="black" stroke-width="2" d="M344.3 223.5c8.9 0 18 5.5 23.7 12.3 3.4 4.1 4.9 10 4.5 15.3-.4 6.7-3.2 13.9-8.2 18.4a27 27 0 0 1-20 6.5c-7.1-.8-13.8-5.4-18.6-10.6a24.4 24.4 0 0 1-1-31.8c4.7-5.7 12.2-10.1 19.6-10z"/>

                 <!-- Кулачок -->
<path fill="grey" stroke="black" stroke-width="3" d="M41.3 153c32.7 0 78.5 3.3 117.6 7 35 3.2 69.9 6.8 104.3 13.2 16.8 3.1 33.3 7.7 49.8 12.2 13 3.5 43.8 11.3 38.7 11.6-20 1.4-39.1 7.2-48.7 22.3-20.3 31.8-.9 54.9-7 41.8-5.4-11.8-18.1-18-29-24.3-13.4-7.9-28.7-11.9-43.5-16.5-19-5.9-38.9-9.2-58.2-14.3-21.5-5.5-42.7-12-64.1-17.4-14-3.5-26.3-5.5-41.9-9.6a25 25 0 0 1-13.2-10.6c-3-4.4-10.1-15.3-4.8-15.3z"/>
             <!-- Цилиндр -->
<path fill="black" d="M377.5 54.3c3.8-.7 50.6-.4 50.6-.4l9.3 10.5v88.8h-66.6V64.4Z"/>
            <!-- Линия фаски -->
<path  stroke="white" d="m370.8 64 66.6.4"/>
              <!-- Верхний рычаг -->
<path fill="grey" stroke="black" stroke-width="3" d="M21.2 153c-3.1 0 1.1-6.2 2.6-9 1.4-2.4 3-5.7 5.9-6.3a17587 17587 0 0 1 341-65.7v81H21.3z"/>

</svg>

如何根据给定的.gif 文件创建凸轮机构的动画?

更新


解决问题时出现问题时提供额外数据。
希望这有帮助。

Inkscape 源代码,创建了该机制的静态 SVG 文件。
SVG 画布大小 500x500 px
可以通过在矢量编辑器中运行 inkscape 源代码来获取部件的尺寸 下面的代码

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   width="500"
   height="500"
   viewBox="0 0 500 500"
   version="1.1"
   id="svg4"
   sodipodi:docname="Заготовка-ink2.svg"
   inkscape:version="0.92.3 (2405546, 2018-03-11)">
  <metadata
     id="metadata10">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title></dc:title>
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <defs
     id="defs8" />
  <sodipodi:namedview
     pagecolor="#ffffff"
     bordercolor="#666666"
     borderopacity="1"
     objecttolerance="10"
     gridtolerance="10"
     guidetolerance="10"
     inkscape:pageopacity="0"
     inkscape:pageshadow="2"
     inkscape:window-width="1440"
     inkscape:window-height="837"
     id="namedview6"
     showgrid="false"
     inkscape:zoom="0.944"
     inkscape:cx="295.12041"
     inkscape:cy="242.72973"
     inkscape:window-x="-8"
     inkscape:window-y="-8"
     inkscape:window-maximized="1"
     inkscape:current-layer="svg4"
     showguides="true"
     inkscape:guide-bbox="true">
    <sodipodi:guide
       position="436.69836,486.88497"
       orientation="0,1"
       id="guide4520"
       inkscape:locked="false" />
    <sodipodi:guide
       position="387.26081,395.5004"
       orientation="1,0"
       id="guide4522"
       inkscape:locked="false" />
    <sodipodi:guide
       position="417.22296,388.75892"
       orientation="1,0"
       id="guide4524"
       inkscape:locked="false" />
    <sodipodi:guide
       position="448.68322,46.441335"
       orientation="0,1"
       id="guide4526"
       inkscape:locked="false" />
    <sodipodi:guide
       position="370.78163,381.26838"
       orientation="1,0"
       id="guide4532"
       inkscape:locked="false" />
    <sodipodi:guide
       position="437.44742,370.03257"
       orientation="1,0"
       id="guide4534"
       inkscape:locked="false" />
    <sodipodi:guide
       position="489.88118,471.15484"
       orientation="0,1"
       id="guide4536"
       inkscape:locked="false" />
    <sodipodi:guide
       position="489.13213,416.47391"
       orientation="0,1"
       id="guide4538"
       inkscape:locked="false" />
    <sodipodi:guide
       position="411.97959,435.94931"
       orientation="0,1"
       id="guide4546"
       inkscape:locked="false" />
    <sodipodi:guide
       position="288.66525,346.92797"
       orientation="0,1"
       id="guide4550"
       inkscape:locked="false" />
    <sodipodi:guide
       position="344.27966,512.1822"
       orientation="1,0"
       id="guide4554"
       inkscape:locked="false" />
    <sodipodi:guide
       position="370.78163,248.94068"
       orientation="0,1"
       id="guide4556"
       inkscape:locked="false" />
  </sodipodi:namedview>
  <image
     
  <path
     style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
     d="m 387.26081,13.11503 h 29.96215 v 440.44364 h -17.97729 -11.98486 z"
     id="path4528"
     inkscape:connector-curvature="0" />
  <path
     style="fill:none;stroke:#0000f7;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
     d="m 387.26081,13.11503 h 29.96215 v 440.44364 h -29.96215 z"
     id="path4530"
     inkscape:connector-curvature="0" />
  <path
     style="fill:none;stroke:#e50000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
     d="m 377.52311,54.312992 c 3.74527,-0.749054 50.56113,-0.374527 50.56113,-0.374527 l 9.36318,10.486753 V 153.18809 H 370.78163 V 64.425218 Z"
     id="path4544"
     inkscape:connector-curvature="0" />
  <path
     style="fill:none;stroke:#570400;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
     d="m 370.78163,64.05069 66.66579,0.374528"
     id="path4548"
     inkscape:connector-curvature="0" />
  <path
     style="fill:none;stroke:#008800;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
     d="m 21.186441,153.07203 c -3.12854,0 1.110117,-6.27995 2.648305,-9.00423 1.413084,-2.50271 3.015439,-5.75628 5.826271,-6.35594 C 135.51425,115.12916 370.78163,72.033898 370.78163,72.033898 v 81.038132 c 0,0 -212.74111,0 -349.595189,0 z"
     id="path4552"
     inkscape:connector-curvature="0"
     sodipodi:nodetypes="sasccs" />
  <path
     style="fill:none;stroke:#0000e7;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
     d="m 344.27966,223.51695 c 8.90907,0.0198 18.05228,5.44187 23.72125,12.31462 3.36808,4.08327 4.85373,9.94633 4.50212,15.22775 -0.44642,6.70562 -3.23009,13.91363 -8.22867,18.40572 -5.21172,4.68364 -13.02457,7.20658 -19.9947,6.48835 -7.07962,-0.72951 -13.76582,-5.31323 -18.53813,-10.59322 -3.49791,-3.87002 -6.03275,-9.09431 -6.35594,-14.30085 -0.37718,-6.07621 1.39653,-12.80421 5.29661,-17.47881 4.70441,-5.63868 12.25403,-10.07987 19.59746,-10.06356 z"
     id="path4558"
     inkscape:connector-curvature="0"
     sodipodi:nodetypes="aaaaaaaaa" />
  <path
     style="fill:none;stroke:#0000ed;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
     d="m 344.27966,197.56356 c 12.26035,-0.30456 25.5788,4.48545 34.42797,12.97669 10.5885,10.16022 17.51589,25.8475 17.21398,40.51907 -0.2794,13.57787 -7.48027,27.60038 -17.4599,36.81145 -8.95578,8.26607 -21.9962,13.17457 -34.18205,12.97669 -13.2941,-0.21588 -27.5112,-5.85832 -36.81144,-15.36017 -8.6229,-8.80982 -13.36567,-22.10127 -13.50636,-34.42797 -0.15236,-13.3485 4.61476,-27.89174 13.77119,-37.60593 9.11148,-9.66649 23.26688,-15.55995 36.54661,-15.88983 z"
     id="path4560"
     inkscape:connector-curvature="0"
     sodipodi:nodetypes="aaaaaaaaa" />
  <path
     style="fill:none;stroke:#00aa40;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
     d="m 41.313559,153.07203 c 32.676369,0 78.490031,3.2645 117.584751,6.8856 34.91059,3.23355 69.86925,6.85792 104.34322,13.24152 16.80002,3.11088 33.30494,7.68524 49.78813,12.18221 12.98636,3.54296 43.73855,11.29999 38.66526,11.65254 -19.95701,1.38685 -39.13749,7.18503 -48.72882,22.24576 -20.23867,31.77966 -0.83862,54.89455 -6.88559,41.84322 -5.46997,-11.80596 -18.20255,-17.97596 -29.13136,-24.3644 -13.36198,-7.81076 -28.65095,-11.82981 -43.4322,-16.4195 -19.0979,-5.93004 -38.9043,-9.28613 -58.26271,-14.30084 -21.4357,-5.55283 -42.60775,-12.10487 -64.08899,-17.47882 -13.877535,-3.47173 -26.30999,-5.43823 -41.843216,-9.5339 -5.465675,-1.44114 -10.056956,-5.92321 -13.241525,-10.59322 -3.020329,-4.42916 -10.127906,-15.36017 -4.76695,-15.36017 z"
     id="path4562"
     inkscape:connector-curvature="0"
     sodipodi:nodetypes="saaasssaaaasas" />
</svg>

矢量编辑器的屏幕截图

在此输入图像描述

笔记:

设计凸轮旋转动画时,请注意旋转的不均匀性:大旋转角度与小角度交替!

相关主题:

  1. 曲柄机构的动画
  2. 如何制作凸轮机构的动画?

比赛将在截止日期后的最后几个小时内结束。
还有很多时间,继续,寻找解决方案!

javascript
  • 4 4 个回答
  • 427 Views

4 个回答

  • Voted
  1. Leonid
    2024-12-15T23:47:29Z2024-12-15T23:47:29Z

    SVG + JS 选项。使用该方法通过对几个点进行两阶段搜索来找到拳头的顶点SVGGeometryElement.getPointAtLength()。点沿着该方法获得的路径长度分布SVGGeometryElement.getTotalLength()。首先,我们粗略地找到 1 个最小y(最高)的点,然后我们再次划分两个相邻点之间的区域,并以更高的精度找到最高点。

    不幸的是,上述方法对路径旋转一无所知。因此,搜索时必须旋转点。为此目的的功能rotatePoint()。

    const kulak = document.getElementById('kulak');
    const piston = document.getElementById('piston');
    
    const init_h = findHighestPoint(kulak, 0).y;
    
    const start_time = performance.now();
    const anim_queue = [{
      start: 0,
      angle: 0
    }, {
      start: 1600,
      angle: -40
    }, {
      start: 3200,
      angle: 0
    }, {
      start: 4200,
      angle: -20
    }, {
      start: 5200,
      angle: 0
    }];
    const anim_total = anim_queue[anim_queue.length - 1].start;
    
    function animate() {
      const time_passed = (performance.now() - start_time) % anim_total;
      const next_i = anim_queue.findIndex(el => el.start > time_passed);
      const angle = ((time_passed - anim_queue[next_i - 1].start) / (anim_queue[next_i].start - anim_queue[next_i - 1].start)) * (anim_queue[next_i].angle - anim_queue[next_i - 1].angle) + anim_queue[next_i - 1].angle;
      rotate(angle);
      requestAnimationFrame(animate);
    }
    
    requestAnimationFrame(animate);
    
    function rotate(angle) {
      kulak.setAttribute('transform', `rotate(${angle})`);
      piston.setAttribute('transform', `translate(0, ${findHighestPoint(kulak, angle).y - init_h})`);
    }
    
    
    function findHighestPoint(element, angle) {
      const len = element.getTotalLength();
      const precision = 1 / 20;
    
      let h_point = rotatePoint(element.getPointAtLength(0), angle);
      let step = len * precision;
      let len_i = 0;
      let cur_point;
    
      for (let i = step; i <= len; i += step) {
        cur_point = rotatePoint(element.getPointAtLength(i), angle);
        if (cur_point.y < h_point.y) {
          h_point = cur_point;
          len_i = i;
        }
      }
    
      for (let j = len_i - step; j <= len_i + step; j += step * precision) {
        cur_point = rotatePoint(element.getPointAtLength(j), angle);
        if (cur_point.y < h_point.y) {
          h_point = cur_point;
        }
      }
    
      return h_point;
    }
    
    function rotatePoint(point, rotate_angle) {
      const angle = Math.atan2(point.y, point.x) + rotate_angle * (Math.PI / 180);
      const r = Math.hypot(point.x, point.y);
    
      point.x = r * Math.cos(angle);
      point.y = r * Math.sin(angle);
    
      return point;
    }
    <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="500px" height="500px" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality;"
    viewBox="-350 -250 500 500"
     xmlns:xlink="http://www.w3.org/1999/xlink">
            <path id="kulak" transform="rotate(0)" fill="dimgrey" stroke="darkslategrey" d="M22.5 -44.05c-0.01,-0.03 -0.04,-0.05 -0.08,-0.08 -69.79,-38.5 -282.86,-50.37 -314.5,-45.84 -2.4,0.43 0.91,12.73 6.93,17.26 27.48,20.66 219.84,38.32 236.96,88.22 6.27,20.61 25.43,35.61 48.09,35.61 27.76,0 50.27,-22.5 50.27,-50.26 0,-19.63 -11.26,-36.63 -27.67,-44.91z"/>
            <path id="center" fill="grey" stroke="darkslategrey" d="M-0.06 -24.58c14.04,0 25.42,11.38 25.42,25.42 0,14.04 -11.38,25.42 -25.42,25.42 -14.04,0 -25.42,-11.38 -25.42,-25.42 0,-14.04 11.38,-25.42 25.42,-25.42zm0 -24.84c27.76,0 50.26,22.5 50.26,50.26 0,27.76 -22.5,50.27 -50.26,50.27 -27.76,0 -50.27,-22.51 -50.27,-50.27 0,-27.76 22.51,-50.26 50.27,-50.26z"/>
            <rect id="bar" fill="grey" stroke="darkslategrey" x="40.87" y="-224.64" width="29.85" height="419.83"/>
            <path id="piston" transform="translate(0,0)" stroke="darkslategrey" fill="grey" d="M23.3 -90.96l64.99 0 0 -84.25 -64.99 0 0 84.25zm0 -84.25l64.99 0 -8.72 -10.11 -47.55 0 -8.72 10.11zm0 84.25c-111.58,0 -223.15,0 -334.73,0 -0.9,-0.22 -0.18,-11.93 9.21,-14.74l325.52 -62.29 0 77.03z"/>
        </svg>

    UPD。为了不深入研究 JS 和实现细节,我添加了一个带有 Web 组件的选项。该属性data-anim包含拳头的旋转角度,格式为:время начала вращения, длительность вращения, начальный угол, конечный угол。单独的动画由符号 分隔|,它们在时间和旋转角度上应该接近(有些冗余)。

    您只需将其添加到标记中<kulak-svg data-anim="0,1600,0,-40|1600,1600,-40,0|3200,1000,0,-20|4200,1000,-20,0"></kulak-svg>即可获得所需的动画。没有 JS,代码片段中的代码被写入,例如,kulak-svg.js您可以将<kulak-svg></kulak-svg>带有必要动画数据的标签插入到页面上。

    例如,插入了 4 个具有不同动画的元素;如果扩展到整个页面,它们将可见。从物理学的角度来看,前两个动画是完全合法的。最后两个将拳头旋转一整圈,因此活塞杆的位置在 -90 到 0 度的位置(以右侧水平面为 0 而言)不合理。这个“错误”是因为我们只是简单地找到了拳头的上点,并用它来设置活塞杆的下边缘,而不管实际的“物体接触”。这样可以更好地展示该方法的优点和缺点。

    还添加了更改的能力viewBox,width并且height对于每个单独的元素,这会覆盖设置为默认值<kulak-svg>的相应属性。<svg><template>

    class KulakSVG extends HTMLElement {
    
      constructor() {
        super();
    
        const template = document.createElement('template');
        template.innerHTML = `
    <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="250px" height="250px" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality;"
    viewBox="-400 -425 750 750"
    xmlns:xlink="http://www.w3.org/1999/xlink">       
            <path id="kulak" transform="rotate(0)" fill="dimgrey" stroke="darkslategrey" d="M22.5 -44.05c-0.01,-0.03 -0.04,-0.05 -0.08,-0.08 -69.79,-38.5 -282.86,-50.37 -314.5,-45.84 -2.4,0.43 0.91,12.73 6.93,17.26 27.48,20.66 219.84,38.32 236.96,88.22 6.27,20.61 25.43,35.61 48.09,35.61 27.76,0 50.27,-22.5 50.27,-50.26 0,-19.63 -11.26,-36.63 -27.67,-44.91z"/>
            <path id="center" fill="grey" stroke="darkslategrey" d="M-0.06 -24.58c14.04,0 25.42,11.38 25.42,25.42 0,14.04 -11.38,25.42 -25.42,25.42 -14.04,0 -25.42,-11.38 -25.42,-25.42 0,-14.04 11.38,-25.42 25.42,-25.42zm0 -24.84c27.76,0 50.26,22.5 50.26,50.26 0,27.76 -22.5,50.27 -50.26,50.27 -27.76,0 -50.27,-22.51 -50.27,-50.27 0,-27.76 22.51,-50.26 50.27,-50.26z"/>
            <rect id="bar" fill="grey" stroke="darkslategrey" x="40.87" y="-425" width="29.85" height="750"/>
            <path id="piston" transform="translate(0,0)" stroke="darkslategrey" fill="grey" d="M23.3 -90.96l64.99 0 0 -84.25 -64.99 0 0 84.25zm0 -84.25l64.99 0 -8.72 -10.11 -47.55 0 -8.72 10.11zm0 84.25c-111.58,0 -223.15,0 -334.73,0 -0.9,-0.22 -0.18,-11.93 9.21,-14.74l325.52 -62.29 0 77.03z"/>
        </svg>
    `;
    
        this.root = this.attachShadow({
          mode: 'closed'
        });
        this.root.append(template.content.cloneNode(true));
      }
    
      connectedCallback() {
        const kulak = this.root.querySelector('#kulak');
        const piston = this.root.querySelector('#piston');
        const svg = this.root.querySelector('svg');
    
        const attribute_vb = this.getAttribute('viewBox');
        if(attribute_vb){svg.setAttribute('viewBox', attribute_vb);}
    
        const attribute_width = this.getAttribute('width');
        if(attribute_width){svg.setAttribute('width', attribute_width);}
    
        const attribute_height = this.getAttribute('height');
        if(attribute_height){svg.setAttribute('height', attribute_height);}
    
        const init_h = findHighestPoint(kulak, 0).y;
    
        const start_time = performance.now();
        const anim_queue = this.getAttribute('data-anim').split('|').map(str => {
          const data = str.split(',');
          return {
            start: +data[0],
            dur: +data[1],
            from: +data[2],
            to: +data[3]
          };
        });
    
    
        const anim_total = anim_queue.reduce((sum, cur) => sum + cur.dur, 0);
    
        function animate() {
          const time_passed = (performance.now() - start_time) % anim_total;
          const cur_anim = anim_queue.findLast(el => el.start < time_passed);
          const angle = ((time_passed - cur_anim.start) / cur_anim.dur) * (cur_anim.to - cur_anim.from) + cur_anim.from;
          rotate(angle);
          requestAnimationFrame(animate);
        }
    
        requestAnimationFrame(animate);
    
    
        function rotate(angle) {
          kulak.setAttribute('transform', `rotate(${angle})`);
          piston.setAttribute('transform', `translate(0, ${findHighestPoint(kulak, angle).y - init_h})`);
        }
    
    
        function findHighestPoint(element, angle) {
          const len = element.getTotalLength();
          const precision = 1 / 20;
    
          let h_point = rotatePoint(element.getPointAtLength(0), angle);
          let step = len * precision;
          let len_i = 0;
          let cur_point;
    
          for (let i = step; i <= len; i += step) {
            cur_point = rotatePoint(element.getPointAtLength(i), angle);
            if (cur_point.y < h_point.y) {
              h_point = cur_point;
              len_i = i;
            }
          }
    
          for (let j = len_i - step; j <= len_i + step; j += step * precision) {
            cur_point = rotatePoint(element.getPointAtLength(j), angle);
            if (cur_point.y < h_point.y) {
              h_point = cur_point;
            }
          }
    
          return h_point;
        }
    
        function rotatePoint(point, rotate_angle) {
          const angle = Math.atan2(point.y, point.x) + rotate_angle * (Math.PI / 180);
          const r = Math.hypot(point.x, point.y);
    
          point.x = r * Math.cos(angle);
          point.y = r * Math.sin(angle);
    
          return point;
        }
      }
    
      disconnectedCallback() {
    
      }
    
    }
    
    customElements.define('kulak-svg', KulakSVG);
    <!-- data-anim format: start,dur,angle_from,angle_to| start,dur,angle_from,angle_to... -->
    
    <kulak-svg viewBox="-350 -250 500 500" data-anim="0,1600,0,-40|1600,1600,-40,0|3200,1000,0,-20|4200,1000,-20,0" ></kulak-svg>
    <kulak-svg data-anim="0,1000,0,15|1000,2000,15,-15|3000,1000,-15,0"></kulak-svg>
    <kulak-svg data-anim="0,15000,0,-360" height="300px" width="300px"></kulak-svg>
    <kulak-svg data-anim="0,15000,0,360"></kulak-svg>

    • 11
  2. DiD
    2024-12-14T14:16:40Z2024-12-14T14:16:40Z

    正如所承诺的。

    SVG+CSS解决方案

    <svg xmlns="http://www.w3.org/2000/svg" style="max-height: 100vh; max-width: 100vw;" viewBox="0 0 500 500" fill="none">
       <style>
          <![CDATA[
          & {
             --cx: 350px;       /* Координаты оси вращения */
             --cy: 250px;
             --cp0y: 200px;     /* высота нижней стороны верхней части системы */
             --dy0: 0px;        /* Начальная позиция (полезно для отсчета относительного смещения)  */
             --scene-time: 4s;  /* Время сцены  */
          }
          
    /* Обычные переменные не могут участвовать в анимациях,  так как области 
    их видимости ограничены местом определения значения + дочерние элементы.
    А у анимаций нет никаких дочерних элемеентов. Поэтому значения, 
    определенные в анимациях никогда не смогут быть получены снаружи.
           
    Другое дело - property. Они имеют одно общее значение на всей странице и 
    не подчиняются каскаду. */
    
          @property --rotation {   /* Угол вращения */
             syntax: '<angle>';    /* Определяем строгий тип - Угол */
             initial-value: 0deg;  /* Начальное значение */
             inherits: false;      /* Получать значение от родителей */
          }
    
          /* Значения угла вращения на каждый интервал таймлайна */
    
          @keyframes rotate {        
             0%  {  --rotation: 0deg;     }       
             16% {  --rotation: 13.5deg;  }        
             18% {  --rotation: 15deg;    }        
             20% {  --rotation: 13.5deg;  }        
             36% {  --rotation: 0deg;     }        
             48% {  --rotation: -13.5deg; }        
             50% {  --rotation: -15deg;   }        
             52% {  --rotation: -13.5deg; }       
             64% {  --rotation: 0deg;     }        
             80% {  --rotation: 13.5deg;  }        
             82% {  --rotation: 15deg;    }        
             84% {  --rotation: 13.5deg;  }        
             100% { --rotation: 0deg;    }
          }
          ]]>
       </style>
       <!-- <image href="image.gif" width="500" height="500" x="1" y="1" preserveAspectRatio="xMidYMid meet" crossorigin="anonymous" /> -->
       <g style="transform: scale(1.1) translate(-28px, -56px);">
          <path style="stroke: #f00; transform-origin: 350px 250px; 
                    transform: rotate(var(--rotation)); 
                    animation: rotate var(--scene-time) linear infinite;"
             d="m 350 200 c -125 -3 -240 15 -305 33 c 2.5 8.5 9.5 13.5 20 15 
                c 10 1 200 9 220 13 c 9 2 17 8 23 14 a 50 50 0 0 1 42 -75z
                a 50 50 0 1 1 -42 +75 m18 -25 a 25 25 0 1 1 50 0 25 25 0 1 1 -50 0" />
          <g stroke="#1D1D5D"
             style="transform: translateY(min(
                   calc(var(--cy) - var(--cp0y) - cos(var(--rotation) - 86.8deg) * 305.5px), 
                   calc(var(--cy) - var(--cp0y) - cos(var(--rotation) - 84.1deg) * 264.4px), 
                   calc(var(--cy) - var(--cp0y) - cos(var(--rotation) - 82.5deg) * 242.1px), 
                   calc(var(--cy) - var(--cp0y) - cos(var(--rotation) - 77.8deg) * 189.3px), 
                   calc(var(--cy) - var(--cp0y) - cos(var(--rotation) - 69.2deg) * 132.6px), 
                   var(--dy0)
                 )); 
                 animation: rotate var(--scene-time) linear infinite;">
             <path d="m 410 200 v 310 h 30 v -310 m 0 -81 v -30 h -30 v 30" />
             <path d="
       m 450 125 v 75 h -405 c 0 0 -1 -12 15 -14 
       c 113 -18 225 -38 340 -58 v -3 h 50 l -6 -6 h -38 l -6 6 v 75" />
          </g>
       </g>
    </svg>

    不幸的是,我无法在动画中使用多个变量(我尝试了一个和不同的变量)。由于某种原因,在动画中,要么一个变量起作用,要么根本不起作用。变量不再接收每 FPS 的平滑值,而是随机接收起始值或结束值。

    使用变量来减少计算量会很棒。不幸的是,所有数学都必须写入 style="..." 参数中,否则您将无法读取动画内定义的值。

    这样我们就得到了剪切变换的计算结果。

    transform: translateY(min(
               calc(var(--cy) - var(--cp0y) - cos(var(--rotation) - 86.8deg) * 305.5px), 
               calc(var(--cy) - var(--cp0y) - cos(var(--rotation) - 84.1deg) * 264.4px), 
               calc(var(--cy) - var(--cp0y) - cos(var(--rotation) - 82.5deg) * 242.1px), 
               calc(var(--cy) - var(--cp0y) - cos(var(--rotation) - 77.8deg) * 189.3px), 
               calc(var(--cy) - var(--cp0y) - cos(var(--rotation) - 69.2deg) * 132.6px), 
               var(--dy0)
    

    其中两列 ( 86.8deg, 305.5px) 是预先计算的常数。从半圆表面取5个点。86.8deg- 这是从旋转轴到选定点的矢量相对于 X 轴(即活塞被抬起的部分的下边界)形成的角度。 A305.5px是这个向量的长度,即旋转点到所选点的距离。

    这些值可以在JS中计算出来。但你可以不用 JS。我们以坐标为 45,223、110,218.5、和 的165,210点为例。226,203350,200

    坐标样本可以从 inkscape 中获取。

    选定点

    让我们列出变量中的坐标值...

    然后我们会做这样的事情:

    <svg xmlns="http://www.w3.org/2000/svg" style="max-height: 100vh; max-width: 100vw;" viewBox="0 0 500 500" fill="none">
       <style>
          <![CDATA[
          :root {
             /* Центр вращения */
             --cx: 350px;
             --cy: 250px;
             /* Уровень нижней границы верхней части */         
             --cp0y: 200px;
             /* Первая точка */
             --cp1x: 45px;
             --cp1y: 233px; 
             /* Вторая точка */
             --cp2x: 110px;
             --cp2y: 218.5px;
             /* Третья точка */
             --cp3x: 165px;
             --cp3y: 210px;
             /* Четвертая точка */
             --cp4x: 226px;
             --cp4y: 203px;
             /* Пятая точка */
             --cp5x: 350px;
             --cp5y: 200px;
             /* Нулевой уровень */
             --dy0: 0px;
             /* Интервал сцены */
             --scene-time: 4s;
          }
    
          @property --rotation {
             syntax: '<angle>';
             initial-value: 0deg;
             inherits: false;
          }
    
          @keyframes rotate {        
             0%  {  --rotation: 0deg;     }       
             16% {  --rotation: 13.5deg;  }        
             18% {  --rotation: 15deg;    }        
             20% {  --rotation: 13.5deg;  }        
             36% {  --rotation: 0deg;     }        
             48% {  --rotation: -13.5deg; }        
             50% {  --rotation: -15deg;   }        
             52% {  --rotation: -13.5deg; }       
             64% {  --rotation: 0deg;     }        
             80% {  --rotation: 13.5deg;  }        
             82% {  --rotation: 15deg;    }        
             84% {  --rotation: 13.5deg;  }        
             100% { --rotation: 0deg;    }
          }
          ]]>
       </style>
       <path style="stroke: #f00; transform-origin: 350px 250px; 
                   transform: rotate(var(--rotation)); 
                   animation: rotate var(--scene-time) linear infinite;"
          d="m 350 200 c -125 -3 -240 15 -305 33 c 2.5 8.5 9.5 13.5 20 15 
             c 10 1 200 9 220 13 c 9 2 17 8 23 14 a 50 50 0 0 1 42 -75z
             a 50 50 0 1 1 -42 +75 m18 -25 a 25 25 0 1 1 50 0 25 25 0 1 1 -50 0" />
       <g stroke="#1D1D5D"
          style="transform: translateY(min(
                calc(var(--cy) - var(--cp0y) - cos(var(--rotation) + atan2(calc(var(--cp1x) - var(--cx)), calc(var(--cy) - var(--cp1y)))) * hypot(calc(var(--cp1x) - var(--cx)), calc(var(--cp1y) - var(--cy)))), 
                calc(var(--cy) - var(--cp0y) - cos(var(--rotation) + atan2(calc(var(--cp2x) - var(--cx)), calc(var(--cy) - var(--cp2y)))) * hypot(calc(var(--cp2x) - var(--cx)), calc(var(--cp2y) - var(--cy)))), 
                calc(var(--cy) - var(--cp0y) - cos(var(--rotation) + atan2(calc(var(--cp3x) - var(--cx)), calc(var(--cy) - var(--cp3y)))) * hypot(calc(var(--cp3x) - var(--cx)), calc(var(--cp3y) - var(--cy)))), 
                calc(var(--cy) - var(--cp0y) - cos(var(--rotation) + atan2(calc(var(--cp4x) - var(--cx)), calc(var(--cy) - var(--cp4y)))) * hypot(calc(var(--cp4x) - var(--cx)), calc(var(--cp4y) - var(--cy)))), 
                calc(var(--cy) - var(--cp0y) - cos(var(--rotation) + atan2(calc(var(--cp5x) - var(--cx)), calc(var(--cy) - var(--cp5y)))) * hypot(calc(var(--cp5x) - var(--cx)), calc(var(--cp5y) - var(--cy)))), 
                var(--dy0)
                )); 
                animation: rotate var(--scene-time) linear infinite;">
          <path d="m 410 200 v 310 h 30 v -310 m 0 -81 v -30 h -30 v 30" />
          <path d="m 450 125 v 75 h -405 c 0 0 -1 -12 15 -14 
                   c 113 -18 225 -38 340 -58 v -3 h 50 l -6 -6 h -38 l -6 6 v 75" />
       </g>
    
    </svg>

    CSS 公式看起来有点生硬。为了解决这个问题,使用了 CSS 函数:cos(), atan2(), hypot()。使用了可变动画@property。

    我会说实话。当我第一次了解到 CSS 中几何函数的存在时,我想我一生中永远找不到在实际操作中使用它们的理由。但它可能是 JS 的一个很好的替代品。

    我差点忘了。

    有一个 SVG 动画与 GIF 的比较。

    <svg xmlns="http://www.w3.org/2000/svg" style="max-height: 100vh; max-width: 100vw;" viewBox="0 0 500 500" fill="none">
       <style>
      <![CDATA[
      & {
         --cy: 250px;
         --cp0y: 200px;
         --dy0: 0px;
         --scene-time: 4s;
      }
    
      @property --rotation {
         syntax: '<angle>';
         initial-value: 0deg;
         inherits: false;
      }
    
          @keyframes rotate {        
             0%  {  --rotation: 0deg;     }       
             16% {  --rotation: 13.5deg;  }        
             18% {  --rotation: 15deg;    }        
             20% {  --rotation: 13.5deg;  }        
             36% {  --rotation: 0deg;     }        
             48% {  --rotation: -13.5deg; }        
             50% {  --rotation: -15deg;   }        
             52% {  --rotation: -13.5deg; }       
             64% {  --rotation: 0deg;     }        
             80% {  --rotation: 13.5deg;  }        
             82% {  --rotation: 15deg;    }        
             84% {  --rotation: 13.5deg;  }        
             100% { --rotation: 0deg;    }
          }
      ]]>
       </style>
       <image href="https://isstatic.askoverflow.dev/JqMRvW2C.gif" width="500" height="500" x="1" y="1" preserveAspectRatio="xMidYMid meet" crossorigin="anonymous" />
       <g style="transform: scale(1.1) translate(-28px, -56px);">
      <path style="stroke: #f00; transform-origin: 350px 250px; 
                transform: rotate(var(--rotation)); 
                animation: rotate var(--scene-time) linear infinite;"
         d="m 350 200 c -125 -3 -240 15 -305 33 c 2.5 8.5 9.5 13.5 20 15 
            c 10 1 200 9 220 13 c 9 2 17 8 23 14 a 50 50 0 0 1 42 -75z
            a 50 50 0 1 1 -42 +75 m18 -25 a 25 25 0 1 1 50 0 25 25 0 1 1 -50 0" />
      <g stroke="#1D1D5D"
         style="transform: translateY(min(
               calc(var(--cy) - var(--cp0y) - cos(var(--rotation) - 86.8deg) * 305.5px), 
               calc(var(--cy) - var(--cp0y) - cos(var(--rotation) - 84.1deg) * 264.4px), 
               calc(var(--cy) - var(--cp0y) - cos(var(--rotation) - 82.5deg) * 242.1px), 
               calc(var(--cy) - var(--cp0y) - cos(var(--rotation) - 77.8deg) * 189.3px), 
               calc(var(--cy) - var(--cp0y) - cos(var(--rotation) - 69.2deg) * 132.6px), 
               var(--dy0)
             )); 
             animation: rotate var(--scene-time) linear infinite;">
         <path d="m 410 200 v 310 h 30 v -310 m 0 -81 v -30 h -30 v 30" />
         <path d="
       m 450 125 v 75 h -405 c 0 0 -1 -12 15 -14 
       c 113 -18 225 -38 340 -58 v -3 h 50 l -6 -6 h -38 l -6 6 v 75" />
      </g>
       </g>
    </svg>

    这可能不是最新版本。

    我没有检查过文字。可能会出现恼人的错误。如果您发现错误,请在不发出警告的情况下更正它。

    • 9
  3. Best Answer
    Stanislav Volodarskiy
    2024-12-14T04:29:41Z2024-12-14T04:29:41Z

    将原来的“铰链”、“凸轮轴”、“凸轮”svg组装成一组driver。该组通过绕轴中心旋转来设置动画。中心是通过眼睛选择的。

    “垂直杆”、“圆柱体”、“倒角线”和“上杠杆”组合成一组drivee,通过脚本垂直移动。 “上臂”标有标识符lever。

    该组driver使用 进行动画处理svg。该函数垂直drive移动组drivee,使底部点lever与顶部点重合drivee。这看起来不错,因为底部边缘lever在屏幕坐标中是水平的。如果杠杆倾斜,则需要进行额外的调整。如果杠杆的工作边缘是弯曲的,则有必要完全改变算法。

    浏览器提供用于计算所描述的矩形的标准工具。但它们工作得不够整齐。该规范并未在任何地方表明所描述的矩形必须是最小的。 Firefox 和 Chrome 都以不同的方式计算它们,并有一定的余量。

    该调用getExtreme([0, -1], driver)计算屏幕坐标中组的顶点driver。计算屏幕坐标中的getExtreme([0, 1], lever)底部点。lever

    getExtreme迭代所有类型为 的节点path。该属性d被解析为其组件命令(函数parsePathD)。为了解决这个问题,您需要能够处理椭圆弧、三次贝塞尔曲线和线段的命令。所有命令都被翻译成可以使用矩阵进行转换的形式。例如,椭圆由其中心和半轴的末端表示。贝塞尔曲线本身由四个点定义。曲线片段显示在屏幕坐标系中(调用node.getScreenCTM()和part.map(matrix))。

    对于每个片段,计算方向上的极值dir。每种片段类型都以自己的方式解决这个问题:

    • 对于一段,选择两端的最大值;
    • dir对于椭圆弧,如果落在弧内,则取圆弧两端和椭圆的两个点(方向上的“上”和“下”)处的值的最大值;
    • 对于贝塞尔曲线,最大值取自曲线两端的值以及导数(二次形式)等于零的所有点的值。

    const solveQuadraticEquation = (a, b, c) => {
        if (a === 0) {
            if (b === 0) {
                return [];
            }
            return [-c / b];
        }
        b /= 2 * a;
        c /= 2 * a;
        const d = b * b - 2 * c;
        if (d < 0) {
            return [];
        }
        if (d === 0) {
            return [-b];
        }
        return [-b - Math.sqrt(d), -b + Math.sqrt(d)]
    };
    
    const makePoint = (x, y) => DOMPointReadOnly.fromPoint({x, y});
    
    const dot = (dir, p) => dir[0] * p.x + dir[1] * p.y;
    
    const makeLine = (p1, p2) => {
        const map = matrix => makeLine(
            p1.matrixTransform(matrix),
            p2.matrixTransform(matrix)
        );
        const getExtreme = dir => Math.max(dot(dir, p1), dot(dir, p2));
        return {map, getExtreme};
    };
    
    const makeCubicBezier = (p1, p2, p3, p4) => {
        const map = matrix => makeCubicBezier(
            p1.matrixTransform(matrix),
            p2.matrixTransform(matrix),
            p3.matrixTransform(matrix),
            p4.matrixTransform(matrix)
        );
        const getExtreme = dir => {
            const v1 = dot(dir, p1);
            const v2 = dot(dir, p2);
            const v3 = dot(dir, p3);
            const v4 = dot(dir, p4);
            const roots = solveQuadraticEquation(
                -v1 + 3 * (v2 - v3) + v4,
                2 * (v1 - 2 * v2 + v3),
                -v1 + v2
            ).filter(x => 0 < x && x < 1);
    
            const values = [v1, v4].concat(roots.map(t => {
                const t2 = t * t;
                const t3 = t * t2;
                const s = 1 - t;
                const s2 = s * s;
                const s3 = s * s2
                return s3 * v1 + 3 * (s2 * t * v2 + s * t2 * v3) + t3 * v4;
            }));
            return Math.max(...values);
        };
        return {map, getExtreme};
    };
    
    const makeArc = (theta1, theta2, c, u, v) => {
        const map = matrix => makeArc(
            theta1, theta2,
            c.matrixTransform(matrix),
            u.matrixTransform(matrix),
            v.matrixTransform(matrix)
        );
        const getExtreme = dir => {
            const value = th => dot(dir, makePoint(
                c.x + Math.cos(th) * (u.x - c.x) + Math.sin(th) * (v.x - c.x),
                c.y + Math.cos(th) * (u.y - c.y) + Math.sin(th) * (v.y - c.y)
            ));
            const inRange = th => (theta1 < theta2) ?
                (theta1 < th && th < theta2) :
                (th < theta2 || theta1 < th)
            ;
            const thetas = [theta1, theta2];
            const th = Math.atan2(
                dot(dir, makePoint(v.x - c.x, v.y - c.y)),
                dot(dir, makePoint(u.x - c.x, u.y - c.y))
            );
            if (inRange(th)) {
                thetas.push(th);
            }
            if (inRange(th + Math.PI)) {
                thetas.push(th + Math.PI);
            }
            return Math.max(...thetas.map(value));
        };
        return {map, getExtreme};
    };
    
    const convertArc = (
        rx, ry, x1, y1, x2, y2, xAxisRotation, largeArcFlag, sweepFlag
    ) => {
        const [px, py] = [(x1 + x2) / 2, (y1 + y2) / 2];
        if (!sweepFlag) {
            [x1, y1, x2, y2] = [x2, y2, x1, y1];
        }
        const a = Math.PI / 180 * xAxisRotation;
        const ca = Math.cos(a);
        const sa = Math.sin(a);
    
        const p_pp = (x, y) => {
            x -= px;
            y -= py;
            return [(ca * x + sa * y) / rx, (-sa * x + ca * y) / ry];
        };
        const pp_p = (xx, yy) => {
            xx *= rx;
            yy *= ry;
            return [px + ca * xx - sa * yy, py + sa * xx + ca * yy];
        };
    
        const [xx1, yy1] = p_pp(x1, y1);
    
        const u = Math.hypot(xx1, yy1);
        const v = (u < 1) ? Math.sqrt(1 - u * u) : 0;
        if (v === 0) {
            rx *= u; 
            ry *= u; 
        }
    
        const f = (largeArcFlag) ? v / u : -v / u;
        const cxx = f * -yy1;
        const cyy = f *  xx1;
    
        const theta1 = Math.atan2( yy1 - cyy,  xx1 - cxx);
        const theta2 = Math.atan2(-yy1 - cyy, -xx1 - cxx);
    
        return makeArc(
            theta1, theta2,
            makePoint(...pp_p(cxx    , cyy    )),
            makePoint(...pp_p(cxx + 1, cyy    )),
            makePoint(...pp_p(cxx    , cyy + 1))
        );
    };
    
    const parseFloats = text => {
        const m = text.match(
            /[-+]?([0-9]*\.[0-9]+|[0-9]+\.[0-9]*|[0-9]+)(e[-+]?[0-9]+)?/gi
        );
        if (m === null) {
            return [];
        }
        return m.map(parseFloat);
    };
    
    const parsePathD = (() => {
        const cmdAbsMove = (context, x, y) => {
            context.initialX = x;
            context.initialY = y;
            context.x = x;
            context.y = y;
            return undefined;
        };
    
        const cmdAbsHLine = (context, x) => {
            const part = makeLine(
                makePoint(context.x, context.y), makePoint(x, context.y)
            );
            context.x = x;
            return part;
        };
    
        const cmdRelVLine = (context, dy) => cmdAbsVLine(context, context.y + dy);
    
        const cmdAbsVLine = (context, y) => {
            const part = makeLine(
                makePoint(context.x, context.y), makePoint(context.x, y)
            );
            context.y = y;
            return part;
        };
    
        const cmdClose = context => {
            context.x = context.initialX;
            context.y = context.initialY;
            return undefined;
        };
    
        const cmdRelCubicBezierCurve = (context, x1, y1, x2, y2, x, y) => {
            const part = makeCubicBezier(
                makePoint(context.x     , context.y     ),
                makePoint(context.x + x1, context.y + y1),
                makePoint(context.x + x2, context.y + y2),
                makePoint(context.x + x , context.y + y )
            );
            context.x += x;
            context.y += y;
            return part;
        };
    
        const cmdAbsArc = (context,
            rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y
        ) => {
            let part;
            if (rx === 0 || ry === 0) {
                part = makeLine(makePoint(context.x, context.y), makePoint(x, y));
            } else {
                part = convertArc(
                    Math.abs(rx), Math.abs(ry),
                    context.x, context.y,
                    x        , y        ,
                    xAxisRotation, largeArcFlag, sweepFlag
                );
            }
            context.x = x;
            context.y = y;
            return part;
        };
    
        const cmdRelArc = (context,
            rx, ry, xAxisRotation, largeArcFlag, sweepFlag, dx, dy
        ) => cmdAbsArc(
            context,
            rx, ry,
            xAxisRotation, largeArcFlag, sweepFlag,
            context.x + dx, context.y + dy
        );
    
        const factory = {
         // 'm': cmdRelMove,
            'M': cmdAbsMove,
         // 'l': cmdRelLine,
         // 'L': cmdAbsLine,
         // 'h': cmdRelHLine,
            'H': cmdAbsHLine,
            'v': cmdRelVLine,
            'V': cmdAbsVLine,
            'z': cmdClose,
            'Z': cmdClose,
         // 'C': cmdAbsCubicBezierCurve,
            'c': cmdRelCubicBezierCurve,
         // 'S': 
         // 's': 
         // 'Q': 
         // 'q': 
         // 'T': 
         // 't': 
            'A': cmdAbsArc,
            'a': cmdRelArc
        };
    
        const parsePathD = d => {
            const context = {
                initialX: 0,
                initialY: 0,
                x: 0,
                y: 0
            };
    
            const parts = [];
    
            for (const c of d.match(/([mlhvzcsqta])([^mlhvzcsqta]*)/gi)) {
                const cb = factory[c[0]];
                const step = cb.length - 1;
                if (step === 0) {
                    const part = cb(context);
                    if (part !== undefined) {
                        parts.push(part);
                    }
                } else {
                    const args = parseFloats(c.substring(1));
                    for (let i = 0; i < args.length; i += step) {
                        const part = cb(context, ...args.slice(i, i + step));
                        if (part !== undefined) {
                            parts.push(part);
                        }
                    }
                }
            }
            return parts;
        };
        
        return parsePathD;
    })();
    
    const getExtremeNode = (dir, node) => {
        const d = node.getAttribute('d');
        const sw = parseFloat(node.getAttribute('stroke-width')) || 0;
        const parts = parsePathD(d);
        const matrix = node.getScreenCTM();
        return sw / 2 + Math.max(
            ...parts.map(part => part.map(matrix).getExtreme(dir))
        );
    };
    
    const getExtreme = (dir, root) => {
        const nodes = [...root.getElementsByTagName('path')];
        if (root.tagName === 'path') {
            nodes.push(root);
        }
        return Math.max(...nodes.map(node => getExtremeNode(dir, node)));
    }
    
    const driver = document.getElementById('driver');
    const drivee = document.getElementById('drivee');
    const lever = document.getElementById('lever');
    
    const drive = () => {
        const h1 = getExtreme([0, -1], driver);
        const h2 = getExtreme([0, 1], lever );
        const y = parseFloat(drivee.getAttribute('y'));
        drivee.setAttribute('y', y - h1 - h2 + 2);
        requestAnimationFrame(drive);
    };
    requestAnimationFrame(drive);
    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400" viewBox="0 0 500 500">
      <svg>
        <g id="driver">
          <!-- Шарнир -->
          <path fill="grey" stroke="black" stroke-width="3" d="M344.3 197.6a49.6 49.6 0 0 1 34.4 13A57 57 0 0 1 396 251a54 54 0 0 1-17.4 36.8c-9 8.2-22 13.1-34.2 13a54.4 54.4 0 0 1-36.8-15.4A51.4 51.4 0 0 1 294 251a55.4 55.4 0 0 1 13.7-37.6 53.7 53.7 0 0 1 36.6-16z"/>
          <!-- Ось кулачка -->
          <path fill="#626262" stroke="black" stroke-width="2" d="M344.3 223.5c8.9 0 18 5.5 23.7 12.3 3.4 4.1 4.9 10 4.5 15.3-.4 6.7-3.2 13.9-8.2 18.4a27 27 0 0 1-20 6.5c-7.1-.8-13.8-5.4-18.6-10.6a24.4 24.4 0 0 1-1-31.8c4.7-5.7 12.2-10.1 19.6-10z"/>
          <!-- Кулачок -->
          <path fill="grey" stroke="black" stroke-width="3" d="M41.3 153c32.7 0 78.5 3.3 117.6 7 35 3.2 69.9 6.8 104.3 13.2 16.8 3.1 33.3 7.7 49.8 12.2 13 3.5 43.8 11.3 38.7 11.6-20 1.4-39.1 7.2-48.7 22.3-20.3 31.8-.9 54.9-7 41.8-5.4-11.8-18.1-18-29-24.3-13.4-7.9-28.7-11.9-43.5-16.5-19-5.9-38.9-9.2-58.2-14.3-21.5-5.5-42.7-12-64.1-17.4-14-3.5-26.3-5.5-41.9-9.6a25 25 0 0 1-13.2-10.6c-3-4.4-10.1-15.3-4.8-15.3z">
        
          </path>
          <animateTransform
            attributeName="transform"
            attributeType="XML"
            type="rotate"
            values="0 347 250;-30 347 250;0 347 250;-10 347 250;0 347 250"
            dur="5s"
            repeatCount="indefinite" />
        </g>
      </svg>
      <svg id="drivee" y="0">
        <!-- Вертикальный стержень -->
        <path fill="grey" stroke="black" stroke-width="3" d="M387.3 13.1h30v440.5h-30z"/>
        <!-- Цилиндр -->
        <path fill="black" d="M377.5 54.3c3.8-.7 50.6-.4 50.6-.4l9.3 10.5v88.8h-66.6V64.4Z"/>
        <!-- Линия фаски -->
        <path  stroke="white" d="m370.8 64 66.6.4"/>
        <!-- Верхний рычаг -->
        <path id="lever" fill="grey" stroke="black" stroke-width="3" d="M21.2 153c-3.1 0 1.1-6.2 2.6-9 1.4-2.4 3-5.7 5.9-6.3a17587 17587 0 0 1 341-65.7v81H21.3z"/>
      </svg>
    </svg>

    • 6
  4. Leonid
    2024-12-14T02:18:47Z2024-12-14T02:18:47Z

    在工程程序中,我们获得了拳头和杠杆的配合边缘的布局。在拳头旋转的极限位置,我们设置与活塞杆的相切。

    给定的臂总长度为 120 毫米,活塞冲程为 40 毫米,我们获得了 180 毫米的转向节边缘半径和一个有用的旋转角度(在这种情况下,程序中的灰色表示从其他尺寸导出的相关尺寸)参数)。

    问题的图形几何

    根据获得的参数,我们在画布上绘制拳头的弧线和活塞杆的水平线。我们建立了对拳头角度变化的依赖,将其限制在 39 度,超过该角度杠杆的位置不会改变。

    杠杆旋转角度变化的依赖性如下:

    Высота перемещения рычага = (угол вращения/общий полезный угол)² * общий ход поршня

    const canvas = document.getElementById('kulak');
    const ctx = canvas.getContext('2d');
    const w = canvas.width = 300;
    const h = canvas.height = 220;
    
    const rx = 280;
    const ry = 90;
    
    const kulak = new Path2D('M 0 0 a 360 360 39 0 0 -226 -80');
    const hor = new Path2D('M 0 0 h -240');
    
    const total_angle = 39;
    const total_move = 80;
    
    const angle_inp = document.getElementById('angle');
    const angle_p = document.getElementById('anglep');
    angle_inp.addEventListener('change', rotate);
    angle_inp.dispatchEvent(new Event('change'));
    
    function rotate(e) {
      const angle = e.target.value;
      angle_p.innerText = angle;
    
      let h_move;
    
      if (angle > 39) {
        h_move = 0;
      } else {
        h_move = Math.pow((total_angle - angle) / total_angle, 2) * total_move;
      }
    
      ctx.clearRect(0, 0, w, h);
    
      ctx.translate(rx, ry);
      ctx.rotate(-angle * (Math.PI / 180));
    
      ctx.stroke(kulak);
      ctx.resetTransform();
    
      ctx.translate(rx, ry - h_move);
      ctx.stroke(hor);
      ctx.resetTransform();
    }
    body {display: flex}
    <canvas id="kulak"></canvas>
    <input type="range" id="angle" min="0" max="50" value="0">
    <p id="anglep"></p>

    UPD。我添加了一个土豆形的拳头 - 没有凸起的复杂曲线。通过简单的搜索,我从上到下、从左到右搜索与拳头形状一致的第一个点。块(“活塞”)被拉至此高度。

    const canvas = document.getElementById('kulak');
    const ctx = canvas.getContext('2d');
    const w = canvas.width = 400;
    const h = canvas.height = 300;
    
    const kulak = new Path2D(`M -66.24 -35.74 c11.78,-0.29 36.06,0.55 44.88,3.15 32.39,9.51 45.53,12.3 46.3,33.43 0.97,26.94 -28.59,31.18 -94.18,31.18 -65.58,0 -68.27,-10.15 -68.88,-27.35 -0.29,-8.21 2.24,-13.89 13.48,-21.89 14.75,-10.5 26.65,-17.75 58.4,-18.52zm66.18 21.58c8.28,0 15,6.72 15,15 0,8.29 -6.72,15 -15,15 -8.29,0 -15,-6.71 -15,-15 0,-8.28 6.71,-15 15,-15z`);
    
    
    
    ctx.fillStyle = 'grey';
    
    let angle = 0;
    
    function rotate() {
      angle += 0.4;
    
      ctx.clearRect(0, 0, w, h);
    
      ctx.translate(w / 2, h / 2);
      ctx.rotate(angle * (Math.PI / 180));
      ctx.fill(kulak, 'evenodd');
      ctx.resetTransform();
      y = -h / 2;
      x = -w / 2;
    
      ctx.rotate(angle * (Math.PI / 180));
    
      draw: for (; y <= 0; y++) {
        for (x = -w / 2; x <= w / 2; x++) {
          if (ctx.isPointInPath(kulak, x, y)) {
            y--;
            break draw;
          }
        }
    
      }
    
    
      ctx.resetTransform();
    
      ctx.fillRect(0, h / 2 + y, w, -30);
    
      requestAnimationFrame(rotate);
    }
    
    requestAnimationFrame(rotate);
    <canvas id="kulak"></canvas>

    • 5

相关问题

  • 第二个 Instagram 按钮的 CSS 属性

  • 由于模糊,内容不可见

  • 弹出队列。消息显示不正确

  • 是否可以在 for 循环中插入提示?

  • 如何将 JSON 请求中的信息输出到数据表 Vuetify vue.js?

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    我看不懂措辞

    • 1 个回答
  • Marko Smith

    请求的模块“del”不提供名为“default”的导出

    • 3 个回答
  • Marko Smith

    "!+tab" 在 HTML 的 vs 代码中不起作用

    • 5 个回答
  • Marko Smith

    我正在尝试解决“猜词”的问题。Python

    • 2 个回答
  • Marko Smith

    可以使用哪些命令将当前指针移动到指定的提交而不更改工作目录中的文件?

    • 1 个回答
  • Marko Smith

    Python解析野莓

    • 1 个回答
  • Marko Smith

    问题:“警告:检查最新版本的 pip 时出错。”

    • 2 个回答
  • Marko Smith

    帮助编写一个用值填充变量的循环。解决这个问题

    • 2 个回答
  • Marko Smith

    尽管依赖数组为空,但在渲染上调用了 2 次 useEffect

    • 2 个回答
  • Marko Smith

    数据不通过 Telegram.WebApp.sendData 发送

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5