如何使用JavaScript确保元素仅在圆上沿一个方向移动? [英] How to make sure elements move only in one direction on circle using javascript?

查看:42
本文介绍了如何使用JavaScript确保元素仅在圆上沿一个方向移动?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

嗯,我承认我对三角学真的很不好.仅出于上下文考虑,我将在此处提及的问题中添加一些内容.

参考问题:).当我单击最后一个元素时,就会发生问题.它标志着整个导航在另一个方向上移动,并在相反方向上完成了完整旋转.

下面是我可以在圆上放置元素的代码:

  const count = this.slides.length;const增加=(Math.PI * 2)/this.slides.length;const radius = 100;令角度= 0;var that = this;this.slides = this.slides.map(function(item,i){item.id = i;item.top = Math.sin(-Math.PI/4 + i *增加)*半径+"px";item.left = Math.cos(-Math.PI/4 + i *增加)*半径+"px";/*我尝试添加角度组件(无法正常工作)*/item.angle = Math.atan2(Math.sin(-Math.PI/4 + i *增加)*半径,Math.cos(-Math.PI/4 + i *增加)*半径);console.log((item.angle * 180)/Math.PI);归还物品;}); 

我想要的是使导航仅向一个方向旋转.我该怎么办?

以下是触发点击旋转的代码:

 函数move(e){const n = buttons.indexOf(e.target);var item = that.slides.find((slide)=> slide.id == n);const endAngle =(n%count)*增加;转动();函数turn(){如果(Math.abs(endAngle-angle)> 1/12){const sign = endAngle>角度 ?1:-1;角度=角度+符号/12;setTimeout(turn,20);} 别的 {角度= endAngle;}button.forEach((button,i)=> {var item = that.slides.find((slide)=> slide.id == n);button.style.top =Math.sin(-Math.PI/4 + i *增加-角度)*半径+"px";button.style.left =Math.cos(-Math.PI/4 + i *增加-角度)*半径+"px";});}} 

我尝试将正负号的值更改为1.但这会使菜单旋转到无限循环.

完整摘要:

 常量按钮= Array.from(document.querySelectorAll('.button'))const count = buttons.lengthconst增加= Math.PI * 2/buttons.length常量半径= 150令角度= 0button.forEach((button,i)=> {button.style.top = Math.sin(-Math.PI/2 + i *增加)*半径+'px'button.style.left = Math.cos(-Math.PI/2 + i *增加)*半径+'px'button.addEventListener('click',移动)})函数move(e){const n = buttons.indexOf(e.target)const endAngle =(n%count)*增加转动()函数turn(){如果(Math.abs(endAngle-angle)> 1/8){const sign = endAngle>角度 ?1:-1角度=角度+符号/8setTimeout(turn,20)} 别的 {角度= endAngle}button.forEach((button,i)=> {button.style.top = Math.sin(-Math.PI/2 + i *增加-角度)*半径+'px'button.style.left = Math.cos(-Math.PI/2 + i *增加-角度)*半径+'px'})}}  

  html,身体 {高度:100%;}.菜单 {高度:100%;显示:-webkit-box;显示:-webkit-flex;显示:-ms-flexbox;显示:flex;背景色:海绿色;-webkit-box-pack:中心;-webkit-justify-content:中心;-ms-flex-pack:中心;证明内容:中心;-webkit-box-align:中心;-webkit-align-items:中心;-ms-flex-align:居中;align-items:居中;行高:100px;文本对齐:居中;}.中央 {宽度:100px;高度:100px;背景色:菊科植物;边界半径:100%;职位:相对}.按钮 {位置:绝对;宽度:100px;高度:100px;边界半径:100%;背景颜色:粉红色;行高:100px;文本对齐:居中;光标:指针;}  

 < div class ="menu">< div class ="center">菜单< 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>  

如果有人可以提供帮助,那就太好了!

解决方案

您不需要三角函数即可解决此问题.这只是算术.除了您已经介绍过的360度成圆形" 部分.

您有一个可以旋转的菜单.还有一些按钮,在旋转时应保持直立.将菜单的旋转放置在CSS变量中,然后使用它来旋转整个菜单 ,以将按钮向后旋转相同的数量.

要定位按钮,请使用一些辅助包装器(实际上是从中心几行),围绕中心点旋转,将按钮放置在该行的另一端,从而有效地将按钮与菜单中心分开.这些辅助对象(轴)的旋转仅需要在开始时进行设置.设置后,它们不再需要调整.它们的旋转量也需要放入一个变量中,因此按钮使用它来反向旋转相同的量.

现在,无论菜单或任何单个轴的旋转如何,按钮都将被相同变量中的值反向旋转,因此始终可以直立.通过更改菜单旋转变量,可以旋转整个对象.另外,通过更改每个轴的旋转值,您可以根据需要创建精美的打开或关闭效果.并且按钮将始终保持直立.

很显然,如果要对整个物体进行动画处理,则运动部件应该共享相同的 transition 属性.如果没有,按钮将不会一直直立.

看到它正常工作:

  const轴= [... document.querySelectorAll('.axis')];axes.forEach((axis,i)=> {常量角度= 360 * i/axes.length;axis.style.setProperty('-axis-rotation',`$ {angle} deg`);})设rotation = 0;//更改它以调整按钮的初始位置updateMenuRotation(rotation);函数updateMenuRotation(deg){document.querySelector('.menu').style.setProperty('-menu-rotation',`$ {deg} deg`);}函数RotateMenu(steps){旋转+ = 360 *步数/axes.length;updateMenuRotation(rotation);console.log('rotation:',rotation);}  

  .menu {宽度:200像素;高度:200px;显示:flex;align-items:居中;证明内容:中心;-菜单旋转:0度;变换:rotation(var(-menu-rotation));}.menu .center {转换:rotate(calc(-1 * var(-menu-rotation)));}.menu .axis {位置:绝对;宽度:100px;左:100px;高度:0;显示:flex;align-items:居中;证明内容:flex-end;transform-origin:0 0;变换:rotation(var(-axis-rotation));}.axis>* {宽度:70像素;高度:70px;显示:flex;align-items:居中;证明内容:中心;border-radius:35px;边框:1px实心#ccc;变换:rotation(calc(calc(-1(var(--axis-rotation))-var(-menu-rotation))));}.菜单,.menu .center,.menu .axis,.menu .axis>* {过渡:转换.35s三次方贝塞尔(.4,0,.2,1);}.controls {z索引:1;职位:相对}.controls按钮{光标:指针;}  

 < div class ="menu">< div class ="center">菜单</div>< div class ="axis">< div> 1</div></div>< div class ="axis">< div> 2</div></div>< div class ="axis">< div> 3</div></div>< div class ="axis">< div> 4</div></div>< div class ="axis">< div> 5</div></div></div>< div class ="controls">< button onclick ="rotateMenu(1)">旋转+ 1</button>< button onclick ="rotateMenu(-1)">旋转-1</button>< button onclick ="rotateMenu(2)">旋转+ 2</button>< button onclick ="rotateMenu(5)">旋转+ 5</button></div>  


现在,据我了解您的尝试,我相信您希望在单击按钮时找到将菜单按钮放在顶部的最小菜单旋转.我故意没有解决上述问题,因为我认为应该与将菜单旋转特定数量的步骤分开解决.

我建议您尝试自己解决它.
如果您遇到问题,以下是我的解决方法.我无法撼动自己过于复杂的感觉,但是我也没有找到进一步简化它的方法:

  const轴= [... document.querySelectorAll('.axis')];axes.forEach((axis,i)=> {常量角度= 360 * i/axes.length;axis.style.setProperty('-axis-rotation',`$ {angle} deg`);axis.querySelector('div').addEventListener('click',rotationToTop);})让旋转= -90;//更改它以调整按钮的初始位置updateMenuRotation(rotation);函数updateMenuRotation(deg){document.querySelector('.menu').style.setProperty('-menu-rotation',`$ {deg} deg`);}函数rotateToTop(e){const button = e.target;如果(按钮){[... document.querySelectorAll('.axis> div.active')].forEach(el => el.classList.remove('active'));button.classList.add('active');rotationMenu(minStepsToTop(getRotation('axis',button),getRotation('menu',button),axes.length));}}函数minStepsToTop(aR,mR,aL){//aR =>axisRotatin//mR =>menuRotation//aL =>轴长//angle =>360/升//stepsFromMenu =>((((mR + 360)%360)+ 90)/角度;//stepsFromAxis =>Math.round(aR/angle);//totalSteps =>Math.round((((((mR + 360)%360)+ 90)+ aR)/angle);const totalSteps = Math.round(((((((mR + 360)%360)+ 90)+ aR)* aL/360);//console.log(totalSteps);//totalSteps为最接近0的数字(正数或负数)const maxAbsoluteSteps = Math.floor(aL/2);//5 =>2;6 =>3;7 =>3,等等.return-(((totalSteps + maxAbsoluteSteps + aL)%aL)-maxAbsoluteSteps);}函数getRotation(type,target){返回+(getComputedStyle(target).getPropertyValue(`-$ {type} -rotation`).replace('deg',''));}函数RotateMenu(steps){旋转+ = 360 *步数/axes.length;updateMenuRotation(rotation);}  

  .menu {宽度:200像素;高度:200px;显示:flex;align-items:居中;证明内容:中心;-菜单旋转:0度;变换:rotation(var(-menu-rotation));}.menu .center {转换:rotate(calc(-1 * var(-menu-rotation)));}.menu .axis {位置:绝对;宽度:100px;左:100px;高度:0;显示:flex;align-items:居中;证明内容:flex-end;transform-origin:0 0;变换:rotation(var(-axis-rotation));}.axis>* {宽度:54像素;高度:54像素;显示:flex;align-items:居中;证明内容:中心;border-radius:27px;边框:1px实心#ccc;变换:rotation(calc(calc(-1(var(--axis-rotation))-var(-menu-rotation))));光标:指针;}.axis .active {背景颜色:#212121;白颜色;}.菜单,.menu .center,.menu .axis,.menu .axis>* {过渡:转换.35s三次方贝塞尔(.4,0,.2,1);}.controls {z索引:1;职位:相对}.controls按钮{光标:指针;}  

 < div class ="menu">< div class ="center">菜单</div>< div class ="axis">< div class ="active"> 1</div></div>< div class ="axis">< div> 2</div></div>< div class ="axis">< div> 3</div></div>< div class ="axis">< div> 4</div></div>< div class ="axis">< div> 5</div></div>< div class ="axis">< div> 6</div></div>< div class ="axis">< div> 7</div></div></div>  

应该可以使用任意数量的按钮,但是我还没有尝试过.

Well, I admit that I am really really bad with trigonometry. Just for the sake of context, I will add things from the question I am referring to here.

Reference question: https://stackoverflow.com/a/39429290/168492.

I am trying to build circular rotating navigation but I have landed in a pickle.

What I want to achieve:

.

I want elements to rotate when you click on any one element. For e.g., Menu Items Are Rotated If The User Selected Item 3:

.

What I have been able to do so far. I have been able to render the items on a circular plot using sin and cos function. I have also been able to achieve circular motion when I click on any of the elements (using the code in the reference question - https://stackoverflow.com/a/39429290/168492). The problem happens when I click on the last element. It marked the whole nav move in another direction and do a complete rotation in opposite direction.

Below is the code using which I was able to position elements on the circle:

const count = this.slides.length;
const increase = (Math.PI * 2) / this.slides.length;
const radius = 100;
let angle = 0;

var that = this;
this.slides = this.slides.map(function (item, i) {
  item.id = i;
  item.top = Math.sin(-Math.PI / 4 + i * increase) * radius + "px";
  item.left = Math.cos(-Math.PI / 4 + i * increase) * radius + "px";
  /* I tried adding an angle component (couldn't get it to work) */
  item.angle = Math.atan2(
    Math.sin(-Math.PI / 4 + i * increase) * radius,
    Math.cos(-Math.PI / 4 + i * increase) * radius
  );
  console.log((item.angle * 180) / Math.PI);
  return item;
});

What I want is to make the nav rotate only in one direction. How do I do it?

Below is the code which triggers the rotation on click:

function move(e) {
  const n = buttons.indexOf(e.target);
  var item = that.slides.find((slide) => slide.id == n);
  const endAngle = (n % count) * increase;

  turn();
  function turn() {
    if (Math.abs(endAngle - angle) > 1 / 12) {
      const sign = endAngle > angle ? 1 : -1;
      angle = angle + sign / 12;
      setTimeout(turn, 20);
    } else {
      angle = endAngle;
    }
    buttons.forEach((button, i) => {
      var item = that.slides.find((slide) => slide.id == n);

      button.style.top =
        Math.sin(-Math.PI / 4 + i * increase - angle) * radius + "px";
      button.style.left =
        Math.cos(-Math.PI / 4 + i * increase - angle) * radius + "px";
    });
  }
}

I have tried changing the sign value to only 1. But it makes the menu rotate into an infinite loop.

Full snippet:

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>

Would be great if someone can help!

解决方案

You don't need trigonometry to solve this problem. It's only arithmetic. Except for the "there are 360 degrees in a circle" part, which you already covered.

You have a menu which rotates. And some buttons which, when rotated, should remain upright. Place the rotation of the menu in a CSS variable and use it to rotate the entire menu and to rotate the buttons back by the same amount.

For positioning the buttons, use some helper wrappers (some lines from the center, really), rotated around the point in the center, placing buttons at the other end of the line, effectively distancing the buttons from the center of the menu. The rotation of these helpers (axis) only needs to be set at the start. Once set, they no longer need adjusting. Their rotation amount also needs to be placed into a variable, so the buttons use it to counter-rotate by the same amount.

Now, regardless of the rotation of the menu or of any individual axis, the buttons, by being counter-rotated by the values in the same variables, will always stand up straight. By changing the menu rotation variable you rotate the entire thing. Also, by changing the rotation value of each axis you can create fancy opening or closing effects, should you want to. And the buttons will always remain upright.

Obviously, if you want to animate the whole thing, the moving parts should share the same transition properties. If they don't, the buttons won't be standing straight at all times.

See it working:

const axes = [...document.querySelectorAll('.axis')];
axes.forEach((axis, i) => {
  const angle = 360 * i / axes.length;
  axis.style.setProperty('--axis-rotation', `${angle}deg`);
})
let rotation = 0; // change it to adjust the initial position of buttons
updateMenuRotation(rotation);

function updateMenuRotation(deg) {
  document.querySelector('.menu').style
    .setProperty('--menu-rotation', `${deg}deg`);
}

function rotateMenu(steps) {
  rotation += 360 * steps / axes.length;
  updateMenuRotation(rotation);
  console.log('rotation:', rotation);
}

.menu {
  width: 200px;
  height: 200px;
  display: flex;
  align-items: center;
  justify-content: center;
  --menu-rotation: 0deg;
  transform: rotate(var(--menu-rotation));
}
.menu .center {
  transform: rotate(calc(-1 * var(--menu-rotation)));
}
.menu .axis {
  position: absolute;
  width: 100px;
  left: 100px;
  height: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  transform-origin: 0 0;
  transform: rotate(var(--axis-rotation));
}
.axis > * {
  width: 70px;
  height: 70px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 35px;
  border: 1px solid #ccc;
  transform: rotate(calc(calc(-1 * var(--axis-rotation)) - var(--menu-rotation)));
}
.menu,
.menu .center, 
.menu .axis,
.menu .axis > * {
  transition: transform .35s cubic-bezier(.4,0,.2,1);
}

.controls {
  z-index: 1;
  position: relative;
}
.controls button {
  cursor: pointer;
}

<div class="menu">
  <div class="center">menu</div>
  <div class="axis">
    <div>1</div>
  </div>
  <div class="axis">
    <div>2</div>
  </div>
  <div class="axis">
    <div>3</div>
  </div>
  <div class="axis">
    <div>4</div>
  </div>
  <div class="axis">
    <div>5</div>
  </div>
</div>

<div class="controls">
  <button onclick="rotateMenu(1)">rotate +1</button>
  <button onclick="rotateMenu(-1)">rotate -1</button>
  <button onclick="rotateMenu(2)">rotate +2</button>
  <button onclick="rotateMenu(5)">rotate +5</button>
</div>


Now, as far as I understand your attempt, I believe you want whenever a button is clicked to find the minimal menu rotation for putting that button on top. I purposefully didn't solve this problem above, as I believe it should be solved separately from the problem of rotating the menu a particular number of steps.

I advise you to try and solve it yourself.
If you get stuck, below is how I solved it. I can't shake the feeling I over-complicated it, but I also don't see a way to further simplify it:

const axes = [...document.querySelectorAll('.axis')];
axes.forEach((axis, i) => {
  const angle = 360 * i / axes.length;
  axis.style.setProperty('--axis-rotation', `${angle}deg`);
  axis.querySelector('div').addEventListener('click', rotateToTop);
})
let rotation = -90; // change it to adjust the initial position of buttons
updateMenuRotation(rotation);

function updateMenuRotation(deg) {
  document.querySelector('.menu').style
    .setProperty('--menu-rotation', `${deg}deg`);
}

function rotateToTop(e) {
  const button = e.target;
  if (button) {
    [...document.querySelectorAll('.axis > div.active')]
      .forEach(el => el.classList.remove('active'));
    button.classList.add('active');
    rotateMenu(
      minStepsToTop(
        getRotation('axis', button),
        getRotation('menu', button), 
        axes.length
      )
    );
  }
}

function minStepsToTop(aR, mR, aL) {
  // aR => axisRotatin
  // mR => menuRotation
  // aL => axis.length
  // angle => 360 / aL
  // stepsFromMenu => (((mR + 360) % 360) + 90) / angle;
  // stepsFromAxis => Math.round(aR / angle);
  // totalSteps => Math.round((((mR + 360) % 360) + 90) + aR) / angle);
  const totalSteps = Math.round(((((mR + 360) % 360) + 90) + aR) * aL / 360);
  // console.log(totalSteps);
  // totalSteps as closest number to 0 (positive or negative)
  const maxAbsoluteSteps = Math.floor(aL / 2); // 5 => 2; 6 => 3; 7 => 3, etc...
  return -(((totalSteps + maxAbsoluteSteps + aL) % aL) - maxAbsoluteSteps);
}

function getRotation(type, target) {
  return +(getComputedStyle(target).getPropertyValue(`--${type}-rotation`).replace('deg', ''));
}

function rotateMenu(steps) {
  rotation += 360 * steps / axes.length;
  updateMenuRotation(rotation);
}

.menu {
  width: 200px;
  height: 200px;
  display: flex;
  align-items: center;
  justify-content: center;
  --menu-rotation: 0deg;
  transform: rotate(var(--menu-rotation));
}
.menu .center {
  transform: rotate(calc(-1 * var(--menu-rotation)));
}
.menu .axis {
  position: absolute;
  width: 100px;
  left: 100px;
  height: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  transform-origin: 0 0;
  transform: rotate(var(--axis-rotation));
}
.axis > * {
  width: 54px;
  height: 54px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 27px;
  border: 1px solid #ccc;
  transform: rotate(calc(calc(-1 * var(--axis-rotation)) - var(--menu-rotation)));
  cursor: pointer;
}
.axis .active {
  background-color: #212121;
  color: white;
}
.menu,
.menu .center, 
.menu .axis,
.menu .axis > * {
  transition: transform .35s cubic-bezier(.4,0,.2,1);
}

.controls {
  z-index: 1;
  position: relative;
}
.controls button {
  cursor: pointer;
}

<div class="menu">
  <div class="center">menu</div>
  <div class="axis">
    <div class="active">1</div>
  </div>
  <div class="axis">
    <div>2</div>
  </div>
  <div class="axis">
    <div>3</div>
  </div>
  <div class="axis">
    <div>4</div>
  </div>
  <div class="axis">
    <div>5</div>
  </div>
  <div class="axis">
    <div>6</div>
  </div>
  <div class="axis">
    <div>7</div>
  </div>
</div>

Should work with any number of buttons, but I haven't tried it.

这篇关于如何使用JavaScript确保元素仅在圆上沿一个方向移动?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆