Как реализовать прокрутку при движении шарика по траектории
Только начала осваивать анимацию с JS.
Подскажите, как лучше будет реализовать данный полукруг, чтобы в дальнейшим подключить анимацию с возможностью скролла и выбора(клика) определённого текста
Ответы (3 шт):
Уверен, что есть более практичные методы на JavaScript. Но можно попробовать набор современных инструментов CSS.
Я использовал offset-path, это путь, принимающий значения SVG-пути. Сгенерировать свой вектор SVG можно с помощью этого иснтрумента.
offset-rotate задает вращение элемента во время прохождения пути. Если не задать - элемент будет проворачиваться, находясь параллельно вектору.
offset-distance это собственно точка на дистанции нашего вектора, используется в анимации.
Надо понимать, что CSS Motion Path несколько сыроват. Но уже имеет право на рассмотрение.
В дальнейшем можно манипулировать CSS и анимацией из JS.
#red-circle {
offset-path: path('M 200 50 C 50 150 50 350 200 450');
offset-rotate: 0deg;
animation: move 3000ms infinite alternate ease-in-out;
width: 40px;
height: 40px;
border-radius: 50%;
background: red;
position: absolute;
}
@keyframes move {
0% {
offset-distance: 0%;
opacity: .7;
transform: scale(0.7);
}
50% {
opacity: 1;
transform: scale(1);
}
100% {
offset-distance: 100%;
opacity: .7;
transform: scale(0.7);
}
}
#red-circle .red-title {
position: absolute;
right: calc(100% + 15px);
font-size: 36px;
line-height: 1;
}
<div id="red-circle">
<div class="red-title">Text</div>
</div>
Обновлено
Так как наш вектор - по сути сектор окружности - можем вести себя с ним соответственно. В вопросе сказано, что Вы осваиваете JavaScript и подключать анимацию и скролл будете в дальнейшем. Не буду лишать Вас этого удовольствия. Просто покажу, как можно общаться с окружностью из JS.
let redCircle = document.querySelector("#red-circle"),
r = 180, // размер окружности 360x360px в CSS
left = '',
tops = '',
alpha = 2 * 3.1415; // не использовал Math.PI, нет необходимости в сверхточности
function drawPosition(t) {
left = r + r * Math.cos(alpha * t); // первый r определяет центр окружности
tops = r + r * Math.sin(alpha * t);
redCircle.setAttribute("style", "left:" + left + "px; top: " + tops + "px; --t: " + t + ";");
// CSS переменная --t добавлена только, чтобы удобно просмотреть в консоли динамику изменения переменной t
}
requestAnimationFrame(function step(time) {
drawPosition(time / 10000); // один оборот за 10 секунд
requestAnimationFrame(step)
});
#red-parent {
width: 360px;
height: 360px;
background: #eee;
border-radius: 50%;
position: relative;
left: 50px;
top: 50px;
margin: 70px;
}
#red-circle {
width: 40px;
height: 40px;
border-radius: 50%;
background: red;
position: absolute;
top: 0;
left: 0;
margin-top: -20px;
margin-left: -20px;
}
#red-circle .red-title {
position: absolute;
right: calc(100% + 15px);
font-size: 36px;
line-height: 1;
}
<div id="red-parent">
<div id="red-circle">
<div class="red-title">Text</div>
</div>
</div>
В данном примере я привязывался к текущему времени и обращался к окружности с помощью startTime и интервальной функции, которая раз в 1мс обновляла текущее время и передавала t в функцию drawPosition(). Но вместо этого можно передавать t вызывая drawPosition() любым другим способом, например, подвязав этот процесс к скроллу.
Логику работы с окружностью подсмотрел в данном видео и адаптировал к ответу.
Решение pure SVG
Решение без скроллинга, так как его нет в SVG, но во втором примере будет скроллинг с использованием JS
Смотрите комментарии в коде, какие строчки реализуют анимацииии движения и увеличения
n
<svg id="svg1" width="400" height="400" viewBox="10 0 400 400" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMinYMin meet" style="border:1px solid grey">
<!-- Траектория движения красного шарика -->
<path id="track" d="m 401,28 c 0,0 -102,33 -103,156 -0.37,115 100,176 100.9,176.6" stroke="green" stroke-width="2" fill="none" id="theMotionPath"/>
<!-- Красный круг, который будет перемещаться по траектории движения. -->
<g id="dot">
<circle cx="0" cy="0" r="10" fill="red" />
<text fill="black" font-size="24" x="-60" y="7">Text</text>
<!-- анимация движения красного шарика -->
<animateMotion
begin="svg1.click"
dur="5s"
repeatCount="indefinite"
fill="freeze">
<mpath xlink:href="#track"></mpath>
</animateMotion>
<!-- анимация увеличения красного шарика -->
<animateTransform
attributeName="transform"
type="scale"
values="1;2;1"
begin="svg1.click"
dur="5s"
repeatCount="indefinite"
additive="sum"/>
</circle>
</g>
</svg>
Решение SVG + JS scroll
<style>
.verylong {
height: 2000px;
}
svg {
position: fixed;
width: 400px;
height: 400px;
}
</style>
<svg width="400" height="400" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMinYMin meet" style="border:1px solid grey">
<!-- Draw the outline of the motion path in grey, along with 2 small circles at key points -->
<path d="m 401,28 c 0,0 -102,33 -103,156 -0.37,115 100,176 100.9,176.6" stroke="green" stroke-width="2" fill="none" id="theMotionPath"/>
<!-- Красный круг, который будет перемещаться по траектории движения. -->
<g id="dot">
<circle cx="0" cy="0" r="10" fill="red" />
<text fill="black" font-size="24" x="-60" y="7">Text</text>
</g>
</svg>
<div class="verylong">
</div>
<script>
function positionTheDot() {
// Какой процент вниз по странице
var scrollPercentage = (document.documentElement.scrollTop + document.body.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
// Получить длину пути
var path = document.getElementById("theMotionPath");
var pathLen = path.getTotalLength();
// Получить положение точки в <scrollPercentage> вдоль пути.
var pt = path.getPointAtLength(scrollPercentage * pathLen);
// Поместите красную точку в эту точку
var dot = document.getElementById("dot");
dot.setAttribute("transform", "translate("+ pt.x + "," + pt.y + ")");
};
// Обновить положение точки при получении события прокрутки.
window.addEventListener("scroll", positionTheDot);
// Установите начальную позицию точки.
positionTheDot();
</script>
const balls = document.querySelectorAll('.ball')
let ballHeight = balls[0].getBoundingClientRect().height
const balls_length = balls.length
const step = Math.PI*2 / balls_length
let counter = 0, current = 0, prev = 0, wait = false
arrange(counter)
addEventListener('mousewheel', wheel)
addEventListener('resize', () => {
ballHeight = balls[0].getBoundingClientRect().height
arrange(counter)
})
function arrange(counter) {
for(let i = 0; i < balls_length; i++) {
const angle = (i + counter) * step
const angle_rotated = Math.PI + angle
const cos = Math.cos(angle_rotated)
const sin = Math.sin(angle_rotated)
const ellipse_y_radius = innerHeight * 0.48
const ellipse_x_radius = (innerWidth + innerHeight) / 2 * 0.2
const ball_y = sin * ellipse_y_radius
const ball_x = cos * ellipse_x_radius
const ellipse_center_y = innerHeight * 0.5 - ballHeight / 2
const ball_y_centered = ball_y + ellipse_center_y
const scale = Math.min(1.4, Math.abs(cos) + 0.4)
const opacity = Math.min(1, Math.abs(cos) + 0.05)
balls[i].style.setProperty('--ty', ball_y_centered + 'px')
balls[i].style.setProperty('--tx', ball_x + 'px')
balls[i].style.setProperty('--sc', scale)
balls[i].style.setProperty('--op', opacity)
balls[i].innerText = i
}
}
function wheel(e) {
if(wait) return
wait = true
setTimeout(() => wait = false, 200)
const direction = e.deltaY > 0.5 ? -1 : 1
counter += direction
counter %= balls_length
prev = current
current = 0
if(counter > 0) current = balls_length - counter
if(counter < 0) current = -1 * counter
arrange(counter)
balls[current].classList.add('active')
balls[prev].classList.remove('active')
}
* {
margin: 0;
padding: 0;
}
body {
height: 100vh;
overflow: hidden;
font-family: sans-serif;
}
.ball {
position: absolute;
top: 0;
right: -5%;
width: calc(10px + 3vw + 3vh);
height: calc(10px + 3vw + 3vh);
margin-left: calc(7px + 3vw);
border-radius: 50%;
background-color: black;
transition: 0.6s;
transform: translate(var(--tx), var(--ty)) scale(var(--sc));
opacity: var(--op);
display: flex;
align-items: center;
justify-content: center;
color: white;
}
.active {
background-color: lightblue;
}
<span class="ball active"></span>
<span class="ball"></span>
<span class="ball"></span>
<span class="ball"></span>
<span class="ball"></span>
<span class="ball"></span>
<span class="ball"></span>
<span class="ball"></span>
<span class="ball"></span>
<span class="ball"></span>
<span class="ball"></span>
