【问题标题】:CSS/Javascript: How to make rotating circular menu with multiple states?CSS/Javascript:如何制作具有多种状态的旋转圆形菜单?
【发布时间】:2021-04-13 16:45:11
【问题描述】:

我通常不会发布自己的帖子,我通常会通过其他人的帖子找到我需要的内容,所以如果其中任何内容出现在错误的位置或格式不正确,我很抱歉。我以前从来没有这样做过。

情况是这样的

我正在尝试重建我的网站,我选择使用 WordPress 的 X 主题。大多数情况下它进展顺利,但有几次我想定制和绕过 X 证明有点困难。如果您知道在 X 中执行此操作而无需进行自定义编码即可完成此操作的方法,我会全力以赴。

这就是我想要做的事情

我有一个圆形菜单的想法,它将它的元素定位到顶部是菜单的“选定”元素的位置。所以它在布局方面看起来像这样:

(抱歉,显然我太新了,无法在帖子中使用图片:/)

基本状态http://i.stack.imgur.com/Gs2Nz.jpg

现在,当用户要单击一个项目时,我希望它将新选定的项目旋转到上一个图像中“1”项目所在的顶部。所以它会是这样的:

如果用户选择了第 3 项,则会旋转菜单项http://i.stack.imgur.com/KWseu.jpg

需要注意的其他事项: 我希望菜单项的文本或图像始终正常对齐,换句话说,我不希望元素文本在旋转后颠倒或其他东西。

页面加载时我希望处理的元素的原始位置,而不是在 CSS 样式中硬编码。主要是为了让它可以动态完成。

我打算对菜单做更多的事情,但我遇到的问题是这种行为。

我已经尝试过类似 Jquery 的 Animate() 方法,或者使用 JavaScript 来影响每个元素的 css "top" 和 "left" 属性,但它似乎并没有工作,因为元素似乎没有想搬家。

我不知道尝试通过 X 的定制器区域是否存在问题,因为我被告知要添加 JavaScript 代码。或者这可能与我没有正确连接 JavaScript/JQuery 代码和 CSS 有关,我有相当多的编码经验,但我对 JQuery/CSS 等比较陌生。

这么短的版本: 我试图找到一种方法,当页面加载时,元素围绕中心点动态定位。然后当用户点击一个元素时,所有元素都围绕中心旋转,直到新选择的项目位于顶部。当用户选择不同的项目时,这种行为应该会继续。

抱歉,这篇文章很长,但我只是尽力解释。任何见解或建议将不胜感激!提前致谢! :)

更新: 所以我最终尝试了 marzelin 的答案,因为它看起来非常适合我想要的。但是,当我将它添加到 X-Theme 的 Javascript 区域并更新我的 CSS 时,元素并没有移动。它们都堆叠在中心,但它们没有包围中心点,点击它们似乎没有做任何事情。似乎 CSS 已生效,但 Javascript 部分由于某种原因没有影响元素?

这是我使用的 marzelin 的答案只是 JavaScript 部分):

const buttons = Array.from(document.querySelectorAll('.button'))
const count = buttons.length
const increase = Math.PI * 2 / buttons.length
const radius = 150
let angle = 0

buttons.forEach((button, i) => {
  button.style.top = Math.sin(-Math.PI / 2 + i * increase) * radius + 'px'
  button.style.left = Math.cos(-Math.PI / 2 + i * increase) * radius + 'px'
  button.addEventListener('click', move)
})

function move(e) {
  const n = buttons.indexOf(e.target)
  const endAngle = (n % count) * increase
  turn()
  function turn() {
    if (Math.abs(endAngle - angle) > 1/8) {
      const sign = endAngle > angle ? 1 : -1
      angle = angle + sign/8
      setTimeout(turn, 20)
    } else {
      angle = endAngle
    }
    buttons.forEach((button, i) => {
      button.style.top = Math.sin(-Math.PI / 2 + i * increase - angle) * radius + 'px'
      button.style.left = Math.cos(-Math.PI / 2 + i * increase - angle) * radius + 'px'
    })
  }
}

这是我的 X-Theme 的 javascript 部分目前的样子不包括其他功能的其他代码,例如隐藏我的导航栏等): p>

jQuery(function($){

/* javascript or jquery code goes here */
  const stars = Array.from(document.querySelectorAll('.btnStars'));
  const count = stars.length;
  const increase = Math.PI * 2 / stars.length;
  const radius = 300;
  let angle = 0;

  stars.forEach((star, i) => {
    star.style.top = Math.sin(-Math.PI / 2 + i * increase) * radius + 'px';
    star.style.left = Math.cos(-Math.PI / 2 + i * increase) * radius + 'px';
    });

  $('.btnStar').click(function(e) {
    const n = stars.indexOf(e.target);
    const endAngle = (n % count) * increase;

    function turn() {
      if (Math.abs(endAngle - angle) > 1/8) {
        const sign = endAngle > angle ? 1 : -1;
        angle = angle + sign/8;
        setTimeout(turn, 20);
      } else {
        angle = endAngle;
      }

      stars.forEach((star, i) => {
        star.style.top = Math.sin(-Math.PI / 2 + i * increase - angle) * radius + 'px';
        star.style.left = Math.cos(-Math.PI / 2 + i * increase - angle) * radius + 'px';
      })
    }

    turn();
  });
});

我确实更改了一些东西,即 CSS 类名称等,但大部分是相同的。我做了一些事情,比如重组,因为 X Theme 的编辑器似乎不知道其中的一些功能是什么,所以我在他们调用之前将它们移到了它们,然后它似乎找到了它们。像这样的小事。

我还尝试将 move 函数更改为 JQuery .click 函数,以查看这是否会触发任何内容,但似乎没有任何改变。

虽然我以前使用过 Javascript 和一些 JQuery,但我从来没有真正尝试过尝试将其合并到 WordPress 主题中,所以我真的不知道这不起作用。

有没有人看到我做错了什么?因为我很困惑为什么这不起作用。 :/

【问题讨论】:

  • Codepen 是你的朋友——codepen.io/tag/circle%20menu
  • 嗯,很有趣。这确实有一些非常接近的想法,我想我可以梳理并尝试将它们组合成我正在寻找的东西。不幸的是,我一直在尝试将不同的东西结合起来,但仍然无法让所有的部分发挥作用。不过,谢谢,这是一个很好的起点! :)

标签: javascript jquery html css wordpress


【解决方案1】:

简单 MVP

const buttons = Array.from(document.querySelectorAll('.button'))
const count = buttons.length
const increase = Math.PI * 2 / buttons.length
const radius = 150

buttons.forEach((button, i) => {
  button.style.top = Math.sin(-Math.PI / 2 + i * increase) * radius + 'px'
  button.style.left = Math.cos(-Math.PI / 2 + i * increase) * radius + 'px'
  button.addEventListener('click', move)
})

function move(e) {
  const n = buttons.indexOf(e.target)
  buttons.forEach((button, i) => {
    button.style.top = Math.sin(-Math.PI / 2 + (i - n % count) * increase) * radius + 'px'
    button.style.left = Math.cos(-Math.PI / 2 + (i - n % count) * increase) * radius + 'px'
  })
}
html,
body {
  height: 100%;
}
.menu {
  height: 100%;
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  background-color: seagreen;
  -webkit-box-pack: center;
  -webkit-justify-content: center;
  -ms-flex-pack: center;
  justify-content: center;
  -webkit-box-align: center;
  -webkit-align-items: center;
  -ms-flex-align: center;
  align-items: center;
}
.center {
  width: 100px;
  height: 100px;
  background-color: goldenrod;
  border-radius: 100%;
  position: relative;
  line-height: 100px;
  text-align: center;
}
.button {
  position: absolute;
  width: 100px;
  height: 100px;
  border-radius: 100%;
  -webkit-transition: all 0.5s;
  transition: all 0.5s;
  background-color: pink;
  line-height: 100px;
  text-align: center;
}
<div class="menu">
  <div class="center">Menu
    <div class="button">1</div>
    <div class="button">2</div>
    <div class="button">3</div>
    <div class="button">4</div>
    <div class="button">5</div>
  </div>
</div>

圆周运动

const buttons = Array.from(document.querySelectorAll('.button'))
const count = buttons.length
const increase = Math.PI * 2 / buttons.length
const radius = 150
let angle = 0

buttons.forEach((button, i) => {
  button.style.top = Math.sin(-Math.PI / 2 + i * increase) * radius + 'px'
  button.style.left = Math.cos(-Math.PI / 2 + i * increase) * radius + 'px'
  button.addEventListener('click', move)
})

function move(e) {
  const n = buttons.indexOf(e.target)
  const endAngle = (n % count) * increase
  turn()
  function turn() {
    if (Math.abs(endAngle - angle) > 1/8) {
      const sign = endAngle > angle ? 1 : -1
      angle = angle + sign/8
      setTimeout(turn, 20)
    } else {
      angle = endAngle
    }
    buttons.forEach((button, i) => {
      button.style.top = Math.sin(-Math.PI / 2 + i * increase - angle) * radius + 'px'
      button.style.left = Math.cos(-Math.PI / 2 + i * increase - angle) * radius + 'px'
    })
  }
}
html, body {
  height: 100%;
}

.menu {
  height: 100%;
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  background-color: seagreen;
  -webkit-box-pack: center;
  -webkit-justify-content: center;
      -ms-flex-pack: center;
          justify-content: center;
  -webkit-box-align: center;
  -webkit-align-items: center;
      -ms-flex-align: center;
          align-items: center;
  line-height: 100px;
  text-align: center;
}

.center {
  width: 100px;
  height: 100px;
  background-color: goldenrod;
  border-radius: 100%;
  position: relative;
}

.button {
  position: absolute;
  width: 100px;
  height: 100px;
  border-radius: 100%;
  background-color: pink;
  line-height: 100px;
  text-align: center;
  cursor: pointer;
}
<div class="menu">
  <div class="center">menu
    <div class="button">1</div>
    <div class="button">2</div>
    <div class="button">3</div>
    <div class="button">4</div>
    <div class="button">5</div>
  </div>
</div>

【讨论】:

  • 哇!那太棒了!这几乎是完美的!反正有没有让底部选项像前三个一样旋转?我刚刚注意到,如果您单击选项 4 或 3,它似乎“跳”到 1,而不是像选项 5 和 2 那样旋转到 1。这仍然很棒,谢谢!它非常接近我想要的东西! :)
  • 太棒了!看起来它正在做我正在寻找的东西,但是,当我将它添加到我的主题代码中时,显然没有采取一些措施。我已经用我使用的代码和我的外观更新了我的原始帖子。不过,我似乎找不到我的适应有什么问题。 ://
  • 好的,所以我设法解决了。我只需要将您的代码转换为 x-theme 可以接受的格式。一旦我这样做了,它最终工作得很好,再次感谢! :)
  • @ASaroWitAGun 你能解决这个问题吗?
【解决方案2】:

这里的三角法感觉不对。

这很像尝试在binary code 中编程。这是可行的,但不一定是我们的程序应该是什么样子,如果我们想保持以后阅读代码的能力并可能进一步修改其逻辑。

为了不必计算每个菜单元素的位置,我们必须将菜单的旋转与每个轴的旋转分开。

一旦将它们分开,它们的值就可以放在 CSS 变量中,旋转它们所针对的元素(菜单或轴),同时将相应的按钮向后旋转相同的量。这样,按钮将始终直立,因为旋转相互抵消。

这是原理的演示。注意 CSS 变量的使用,
使用style="{ '--var-name': value }"。您还可以在运行时检查标记以读取当前旋转值:

new Vue({
  el: '#app',
  data: () => ({
    buttons: 3,
    useTransitions: true,
    isMenuOpen: true,
    rotation: -90
  }),
  computed: {
    axisRotations() {
      return Array.from({
        length: this.buttons
      }).map((_, i) => 360 * (this.buttons - i) / this.buttons)
    },
    menuRotation: {
      get() {
        return this.rotation
      },
      set(val) {
        this.rotation = isNaN(Number(val)) ? -90 : Number(val)
      }
    }
  },
  methods: {
    updateButtons(n) {
      if (this.buttons + n > 0) {
        this.buttons += n;
        this.isMenuOpen = true;
        this.menuRotation = -90;
      }
    },
    goToTop(axis) {
      let diff = this.degreesToTop(axis);
      diff = diff > 180
        ? diff - 360
        : diff <= -180
          ? diff + 360
          : diff;
      this.menuRotation = Math.round((this.menuRotation + diff) * 10) / 10;
    },
    degreesToTop(axis) {
      return (Math.round(this.axisRotations[axis - 1]) - this.menuRotation - 90) % 360;
    },
    isActive(axis) {
      return !(this.degreesToTop(axis));
    },
    toggleMenu() {
      this.isMenuOpen = !this.isMenuOpen;
    }
  }
})
.menu {
  width: 0;
  height: 0;
  top: 110px;
  left: 110px;
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  transform: rotate(var(--menu-rotation));
  --menu-rotation: 0deg;
}

.menu .center {
  height: 54px;
  min-width: 54px;
  border-radius: 27px;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: white;
  border: 1px solid #eee;
  cursor: pointer;
  z-index: 2;
  transform: rotate(calc(-1 * var(--menu-rotation))) translateZ(0);
}

.menu .axis {
  position: absolute;
  width: 100px;
  left: 0;
  height: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  transform-origin: 0 0;
  transform: rotate(var(--axis-rotation));
}

.animated .axis.axis {
  transition: all .54s cubic-bezier(.4, 0, .2, 1);
}

.menu .axis.closed {
  width: 27px;
  transform: rotate(calc(var(--axis-rotation) + 180deg));
  opacity: .1;
}

.axis.closed button,
.axis.active button {
  color: white;
  background-color: #f50;
}

.axis.active:not(.closed) {
  z-index: 1;
}

.axis button {
  background-color: white;
  cursor: pointer;
  width: 54px;
  height: 54px;
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 27px;
  border: 1px solid #eee;
  transform: rotate(calc(calc(-1 * var(--axis-rotation)) - var(--menu-rotation))) translateZ(0);
  outline: none;
}

.flexer {
  display: flex;
  height: 240px;
  padding-left: 220px;
}

.controls {
  flex-grow: 1
}

input {
  width: 100%;
}

label input {
  width: auto;
}

label {
  display: block;
  margin-top: 1rem;
  cursor: pointer;
}

.animated,
.animated .center,
.animated .axis,
.animated .axis>* {
  transition: transform .35s cubic-bezier(.4, 0, .2, 1);
}

body {
  background-color: #f8f8f8;
}
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
<div id="app">
  <div>
    <div class="flexer">
      <div class="menu"
           :class="{ animated: useTransitions }"
           :style="{'--menu-rotation': `${menuRotation}deg`}">
        <div class="center" @click="toggleMenu">menu</div>
        <div v-for="axis in buttons"
             class="axis"
             :class="{ closed: !isMenuOpen, active: isActive(axis) }"
             :style="{'--axis-rotation': `${360 * (axis - 1) / buttons}deg`}">
          <button v-text="axis" @click="goToTop(axis)" />
        </div>
      </div>
      <div class="controls">
        Menu rotation (<code v-text="`${menuRotation}deg`"></code>)
        <input type="range" min="-720" max="720" v-model="menuRotation">
        <button @click="updateButtons(1)">Add button</button>
        <button @click="updateButtons(-1)">Remove button</button>
        <button @click="toggleMenu">Toggle menu</button>
        <label>
          <input type="checkbox" v-model="useTransitions">Use transitions
        </label>
      </div>
    </div>
    <pre v-text="{ menuRotation, buttons, axisRotations }"></pre>
  </div>
</div>

如您所见,我从不计算按钮的位置。唯一使用的三角函数是“一个圆有 360 度”

上面的例子是在 Vue 中完成的,因为它是我碰巧喜欢的快速原型制作工具。如果您想要一个将项目置于顶部的普通解决方案,请参阅 my answer 了解此问题的后续问题。

【讨论】:

猜你喜欢
  • 2021-11-20
  • 2012-11-03
  • 2017-08-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-04
  • 1970-01-01
  • 2014-01-27
相关资源
最近更新 更多