SVG|绘制圆形

主题文件编辑器:function

function svg_business_map_shortcode() {
  ob_start();
  ?>
  <div class="svg-business-wrap">

    <!-- SVG -->
    <div class="svg-area">
      <svg viewBox="0 0 1000 1000" class="business-svg">
        <circle class="circle circle-a" cx="400" cy="400" r="320" />
        <circle class="circle circle-b" cx="580" cy="360" r="260" />
        <circle class="circle circle-c" cx="320" cy="520" r="220" />

        <circle class="node" data-target="a" cx="140" cy="260" r="8" />
        <circle class="node" data-target="b" cx="700" cy="300" r="8" />
        <circle class="node" data-target="c" cx="260" cy="720" r="8" />
      </svg>
    </div>

    <!-- 内容 -->
    <div class="panel">
      <div class="content active" data-id="a">
        <h3>project1</h3>
        <p>content1</p>
      </div>
      <div class="content" data-id="b">
        <h3>project2</h3>
        <p>content2</p>
      </div>
      <div class="content" data-id="c">
        <h3>project3</h3>
        <p>content3</p>
      </div>
    </div>

  </div>
  <?php
  return ob_get_clean();
}
add_shortcode('svg_business_map', 'svg_business_map_shortcode');


function svg_business_map_scripts() {
  wp_enqueue_script(
    'svg-business-map',
    get_theme_file_uri('/svg-business-map.js'),
    [],
    false,
    true
  );
}
add_action('wp_enqueue_scripts', 'svg_business_map_scripts');

服务器端public_html/wp-content/themes/twentytwentyfive
新建:svg-business-map.js

document.addEventListener('DOMContentLoaded', function () {
  const wrap = document.querySelector('.svg-business-wrap');
  if (!wrap) return;

  const circles = wrap.querySelectorAll('.circle');

  // 初始化描边隐藏
  circles.forEach(circle => {
    const length = circle.getTotalLength();
    circle.style.strokeDasharray = length;
    circle.style.strokeDashoffset = length;
  });

  // 滚动监听
  const observer = new IntersectionObserver(
    entries => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          animateCircles();
          observer.disconnect(); // 只执行一次
        }
      });
    },
    {
      threshold: 0.4 // 进入40%才触发
    }
  );

  observer.observe(wrap);

  function animateCircles() {
    circles.forEach((circle, index) => {
      setTimeout(() => {
        circle.style.transition = "stroke-dashoffset 1.6s ease";
        circle.style.strokeDashoffset = 0;
      }, index * 300); // 每个圆延迟300ms
    });
  }

  /* =====================
     下面是你原来的点击逻辑
  ===================== */

  const nodes = wrap.querySelectorAll('.node');
  const contents = wrap.querySelectorAll('.content');

  const circleMap = {
    a: wrap.querySelector('.circle-a'),
    b: wrap.querySelector('.circle-b'),
    c: wrap.querySelector('.circle-c'),
  };

  function activate(key) {
    Object.values(circleMap).forEach(c => c.classList.remove('active'));
    circleMap[key].classList.add('active');

    contents.forEach(c => c.classList.remove('active'));
    wrap.querySelector('.content[data-id="' + key + '"]').classList.add('active');
  }

  nodes.forEach(node => {
    const key = node.dataset.target;
    node.addEventListener('click', () => activate(key));
    node.addEventListener('touchstart', () => activate(key));
  });
});

circle.style.opacity = 1;

setTimeout(() => {
  wrap.querySelector('.circle-a').classList.add('active');
}, circles.length * 300 + 800);

自定义额外CSS

.svg-business-wrap {
  display: flex;
  gap: 40px;
  align-items: center;
}

/* SVG 区域 */
.svg-area {
  flex: 1;
}

.business-svg {
  width: 100%;
  max-width: 580px;
}

/* 圆样式 */
.circle {
  fill: none;
  stroke: #ccc;
  stroke-width: 3;
  transition: stroke 0.3s ease, stroke-width 0.3s ease;
}

.circle.active {
  stroke: #e60012;
  stroke-width: 5;
}

/* 节点 */
.node {
  fill: #fff;
  stroke: #e60012;
  stroke-width: 2;
  cursor: pointer;
}

/* 右侧内容 */
.panel {
  flex: 1;
  max-width: 360px;
}

.content {
  display: none;
}

.content.active {
  display: block;
}

/* =====================
   📱 手机端优化
===================== */
@media (max-width: 768px) {
  .svg-business-wrap {
    flex-direction: column;
  }

  .panel {
    max-width: 100%;
    margin-top: 20px;
  }

  .business-svg {
    max-width: 100%;
  }
}



.circle {
  fill: none;
  stroke: #ccc;
  stroke-width: 3;

  stroke-dasharray: 2000;
  stroke-dashoffset: 2000;

  animation: drawCircle 2s ease forwards;
}

@keyframes drawCircle {
  to {
    stroke-dashoffset: 0;
  }
}

.circle-a {
  animation-delay: 0s;
}

.circle-b {
  animation-delay: 0.4s;
}

.circle-c {
  animation-delay: 0.8s;
}





.circle {
  fill: none;
  stroke: #ccc;
  stroke-width: 3;
}

画好图后,发现主圆有一处缺口

检查了viewBox、 circle,发现不是裁切的问题。
是动画参数错误
CSS是:
stroke-dasharray: 2000;
stroke-dashoffset: 2000;
但主圆真实周长 ≈ 2011(r = 320 2 × 3.1416 × 320 ≈ 2010.6)
所以画主圆时 dasharray 不够完整一圈,就会留下一段空白“缺口”。

→ 修改主题文件编辑器:function

<circle
  class="circle circle-a"
  cx="400"
  cy="400"
  r="320"
  pathLength="1"
/>

CSS

.circle {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: drawCircle 2s ease forwards;
}

直接把路径长度(pathLength)定义为1
画圆时自动把 1 当成完整周长
这样不用计算圆周长,修改半径也不受影响。