Создаем разметку HTML:
Пишем стиль CSS:
Добавляем динамику через jquery:
Смотрим наш результат:
HTML:
<svg id="demo" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 0 0" width="0" height="0" stroke-linecap="round" stroke-linejoin="round">
<rect id="background" x="0" y="0" width="100%" height="100%" fill="transparent" />
<g id="outlines"></g>
<g id="text"></g>
<g id="circles"></g>
</svg>
<div id="wrap">
<div class="panel"><img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/314556/squareDog1.jpg"></div>
<div class="panel"><img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/314556/squareDog2.jpg"></div>
<div class="panel"><img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/314556/squareDog3.jpg"></div>
<div class="panel"><img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/314556/squareDog4.jpg"></div>
<div class="panel"><img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/314556/squareDog5.jpg"></div>
<div class="panel"><img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/314556/squareDog6.jpg"></div>
</div>
CSS:
* {
box-sizing: border-box;
}
body {
height: 100vh;
width: 100%;
padding: 0;
margin: 0;
overflow: hidden;
font-family: "Roboto", sans-serif;
}
.panel {
height: 100%;
width: 100%;
border-radius: 100%;
overflow: hidden;
position: absolute;
top: 0;
left: 0;
border: solid 10px white;
}
.panel img {
width: 100%;
height: 100%;
}
#demo {
position: absolute;
width: auto;
height: auto;
max-height: 140px;
max-width: 90%;
font-weight: 700;
left: 50%;
bottom: 20px;
opacity: 0;
}
#circles path {
cursor: pointer;
}
#wrap {
width: 50%;
height: 50%;
left: 50%;
position: absolute;
border: solid 10px #42a6e0;
border-radius: 100%;
overflow: hidden;
opacity: 0;
}
JavaScript:
console.clear();
TweenLite.defaultEase = Linear.easeNone;
var demo = document.querySelector("#demo");
var groups = [document.querySelector("#demo #outlines"), document.querySelector("#demo #circles") ];
var textGroup = document.querySelector("#demo #text");
var textElements = [];
var svgns = "http://www.w3.org/2000/svg";
var panels = document.querySelectorAll(".panel");
// *********************** adjustable stuff ***********************
var count = panels.length; // how many circles - automatically adjusts to number of panels
var r = 100; // circle radius
var spacer = 25; // space between circles
var strokes = [2, 12]; // [outline stroke width, animating stroke width]
var circleFill = ["transparent", "transparent"]; // [outline circle fill, animating circle fill]
var colors = ["#000", "#42a6e0"]; // [outline stroke color, animating stroke color]
var opacity = [0.2, 1]; // [outline stroke opacity, animating stroke opacity]
var fs = r/2; // font-size
// ***********************************************************
var middles = []; // array of circle centers
var oldTarget = 0; // starting target
var newTarget = 0; // new target
var tl; // timeline
var master; // timeline
var oldDraw; // circle animation direction calculation variable
var newDraw; // circle animation direction calculation variable
var lineTarget; // line calculation percentage variable
var stretchTarget; // line calculation percentage variable
var demoHeight = r * 2 + strokes[1];
var demoWidth = (r * 2 + spacer) * count - spacer + strokes[1];
// trim the SVG to the circles & position gallery panels
TweenMax.set(demo, { attr: { viewBox: "0 0 " + demoWidth + " " + demoHeight, width:demoWidth, height:demoHeight }, xPercent:-50 });
TweenMax.set("#wrap", {xPercent:-50, yPercent:-50});
TweenMax.set(panels, {rotation:180, transformOrigin:"center bottom"});
TweenMax.set(panels[0], {rotation:0});
// loops to create the circles, text and lines needed
for (let j = 0; j < 2; j++) {
for (let i = 0; i < count; i++) {
var middle = i * (r * 2) + r + spacer * i + strokes[1] / 2;
if (j === 0) {
middles.push(middle);
}
var circle = document.createElementNS(svgns, "circle");
circle.setAttributeNS(null, "cx", middle);
circle.setAttributeNS(null, "cy", demoHeight/2);
circle.setAttributeNS(null, "r", r);
groups[j].appendChild(circle);
TweenLite.set(circle, {
stroke: colors[j],
strokeWidth: strokes[j],
opacity: opacity[j],
fill: circleFill[j]
});
if (j === 0) {
var txt = document.createElementNS(svgns, "text");
txt.setAttributeNS(null, "x", middle);
txt.setAttributeNS(null, "y", demoHeight/2);
txt.setAttributeNS(null, "font-size", fs);
txt.setAttributeNS(null, "text-anchor", "middle");
txt.setAttributeNS(null, "fill", colors[0]);
txt.setAttributeNS(null, "opacity", opacity[0]);
txt.setAttributeNS(null, "dominant-baseline", "central"); //MS Edge doesn't like this
txt.innerHTML = i + 1;
textGroup.appendChild(txt);
textElements.push(txt);
}
}
var line = document.createElementNS(svgns, "line");
line.setAttributeNS(null, "x1", middles[0]);
line.setAttributeNS(null, "x2", middles[count - 1]);
line.setAttributeNS(null, "y1", demoHeight/2 + r);
line.setAttributeNS(null, "y2", demoHeight/2 + r);
groups[j].appendChild(line);
TweenLite.set(line, {
fill: "none",
stroke: colors[j],
strokeWidth: strokes[j],
opacity: opacity[j]
});
}
// convert the circles to paths so the correct 6 o'clock starting point works in all browsers
MorphSVGPlugin.convertToPath("#circles circle");
var targets = document.querySelectorAll("#circles path");
// make targets from the paths, add listeners
for (let i = 0; i < targets.length; i++) {
targets[i].index = i;
targets[i].addEventListener("click", doCoolStuff);
}
var lineA = document.querySelectorAll("line")[1]; // target for animating line
TweenMax.set(targets, {rotation: 90, transformOrigin: "center center", drawSVG: 0}); // start at 6 o'clock
TweenMax.set(targets[0], {drawSVG: true}); // highlight starting circle
TweenMax.set(textElements[0], {fill:colors[1], opacity:opacity[1]}); // highlight starting text
var startMiddle = middles[0];
TweenMax.set(lineA, {attr:{x1:middles[0], x2:middles[0]}}); // zero out the animating line
var cf = DrawSVGPlugin.getLength( targets[0] ); // circumference needed for durtion calculations
// do all the fancy stuff on click
function doCoolStuff() {
newTarget = this.index;
if (oldTarget === newTarget) {
return;
}
if (master && master.isActive()) {
master.progress(1);
}
master = new TimelineMax(); // main timeline
// figure which way the old and new circle need to draw on/off depending on the travel direction of the line.
if (newTarget > oldTarget) {
// traveling left to right
oldDraw = { drawSVG: "0% 0%" };
newDraw = { drawSVG: "100% 100%" };
lineTarget = middles[oldTarget] + cf;
stretchTarget = middles[newTarget] - cf;
} else {
// traveling right to left
oldDraw = { drawSVG: "100% 100%" };
newDraw = { drawSVG: "0% 0%" };
lineTarget = middles[oldTarget] - cf;
stretchTarget = middles[newTarget] + cf;
}
var dur = 10; // duration value is irrelevant since actual animation is a progress() tween
var distance = Math.abs(startMiddle - middles[newTarget]); // how far away is the clicked target
startMiddle = middles[newTarget]; // middle point of target circle
var totalDistance = distance + cf; // travel distance plus circumference of the circle
var circDur = cf / totalDistance * dur; // duration for the circle unwrap/wrap
tl = new TimelineMax({ paused: true }); // line animation timeline
tl.fromTo(targets[oldTarget], circDur, { drawSVG: "0% 100%" }, oldDraw); // erase active circle
if (distance > cf) {
// line needs to 'unwrap' and travel a bit by itself before wrapping around new target
tl.to(lineA, circDur, {attr:{x1:middles[oldTarget], x2:lineTarget}}, 0);
tl.to(lineA, dur-circDur*2, {attr:{x1:stretchTarget, x2:middles[newTarget]}});
tl.to(lineA, circDur, {attr:{x1:middles[newTarget], x2:middles[newTarget]}}, "part2");
} else {
// line needs to start wrapping around new target before fully unwrapped on old target
tl.to(lineA, dur - circDur, {attr:{x1:middles[oldTarget], x2:middles[newTarget]}}, 0);
tl.add("part2", dur - circDur);
tl.to(lineA, dur - circDur, {attr:{x1:middles[newTarget], x2:middles[newTarget]}}, circDur);
}
tl.fromTo(targets[newTarget], circDur, newDraw, {drawSVG:"0% 100%"}, "part2"); // draw on target circle
tl.progress(1).progress(0);
master.to(tl, 1, {progress:1, ease:Power2.easeInOut});
master.to(textElements[oldTarget], 1, {fill:colors[0], opacity:opacity[0]}, 0);
master.to(textElements[newTarget], 1, {fill:colors[1], opacity:opacity[1]}, 0);
master.to(panels[oldTarget], 1, {rotation:-180, ease:Back.easeIn}, 0);
master.fromTo(panels[newTarget], 1, {rotation:180}, {rotation:0, ease:Back.easeOut}, "-=0.35");
oldTarget = newTarget; // flip values for next click;
}
// center gallery in the available space above the circle controls
function newSize() {
var wh = window.innerHeight - demo.getBoundingClientRect().height;
var ww = window.innerWidth;
TweenMax.set("#wrap", { top: wh / 2 });
var factor = 0.88;
if (wh > ww) {
TweenMax.set("#wrap", { width: ww * factor, height: ww * factor });
} else {
TweenMax.set("#wrap", { width: wh * factor, height: wh * factor });
}
}
TweenMax.set("#wrap, #demo", { autoAlpha: 1 });
newSize();
window.addEventListener("resize", newSize);