// Function to get a path (string) similar to sin function. Can accept following options that you can use for customization: // - width: Width to draw the path // - height: Height to draw the path // - addWidth: Additional width to overflow actual width // - controlSep: Bigger values of this parameter will add more curvature, and vice versa // - curves: Number of curves (iterations) to draw function PathSlider(t, i, n) { this.path = is.str(t) ? document.querySelector(t) : t, this.pathLength = this.path.getTotalLength(), this.items = is.str(i) ? document.querySelectorAll(i) : i, this.itemsLength = this.items.length, this.init(n) } function setStyle(t, i, n) { t.style[i] = n, t.style["-webkit-" + i] = n } function call(t, i) { is.fnc(t) && t(i) } function extendSingle(t, i) { for (var n in i) t[n] = is.arr(i[n]) ? i[n].slice(0) : i[n]; return t } function extend(t, i) { t || (t = {}); for (var n = 1; n < arguments.length; n++) extendSingle(t, arguments[n]); return t } PathSlider.prototype = { defaults: { paddingSeparation: 0, duration: 1e3, delay: 0, stagger: 0, easing: "easeInOutCubic", elasticity: void 0, rotate: !1, begin: void 0, end: void 0, beginAll: void 0, endAll: void 0, clickSelection: !0 }, init: function(t) { this.initialOptions = t, extend(this, this.defaults, t), this.initPathOptions(), this.initItems(), this.clickSelection && this.initEvents() }, initPathOptions: function() { this.activeSeparation = is.und(this.initialOptions.activeSeparation) ? (this.pathLength - 2 * this.paddingSeparation) / this.itemsLength: this.initialOptions.activeSeparation, is.und(this.initialOptions.startLength) ? this.startLength = this.paddingSeparation + this.activeSeparation / 2 : this.startLength = "center" === this.initialOptions.startLength ? this.pathLength / 2 : this.initialOptions.startLength + this.paddingSeparation }, initItems: function() { for (var t = [], i = 0; i < this.itemsLength; i++) t.push({ node: this.items[i], positionIndex: i }); this.items = t, this.currentIndex = 0, this.updatePositions(), this.updateClass(), this.animations = [] }, initEvents: function() { for (var t = this, i = 0; i < this.itemsLength; i++) ! function(i) { t.items[i].node.addEventListener("click", function() { // t.selectItem(i) }) } (i) }, updatePositions: function() { this.calcPositions(); for (var t, i = 0; i < this.itemsLength; i++) t = this.items[i], t.position = this.positions[t.positionIndex], this.setPosition(t.node, t.position) }, calcPositions: function() { this.positions = [], this.pathLength = this.path.getTotalLength(), this.initPathOptions(); for (var t = this.pathLength - 2 * this.activeSeparation - 2 * this.paddingSeparation, i = t / (this.itemsLength - 2), n = this.startLength, e = 0; e < this.itemsLength; e++) this.positions.push(n), n += 0 === e ? this.activeSeparation: i, n >= this.pathLength - this.paddingSeparation && (n += 2 * this.paddingSeparation, n -= this.pathLength) }, setPosition: function(t, i) { var n = this.point(i), e = this.point(i - 1), s = this.point(i + 1), o = ["translate(" + n.x + "px, " + n.y + "px)"]; if (this.rotate) { var a = 180 * Math.atan2(s.y - e.y, s.x - e.x) / Math.PI + 180; o.push("rotate(" + a + "deg)") } setStyle(t, "transform", o.join(" ")) }, point: function(t) { return this.path.getPointAtLength(this.getRealPosition(t)) }, // selectItem: function(t) { // var i = this.items[t], // n = i.positionIndex, // e = !0; // if (0 !== n) { // for (var s = 0; s < this.animations.length; s++) this.animations[s].anime.pause(); // this.animations = [], // this.updateClass(t), // n > this.itemsLength / 2 && (e = !1); // for (var o = this, // a = 0; a < this.itemsLength; a++) ! // function(i) { // var s = o.items[i], // a = i - t < 0 ? o.itemsLength - (t - i) : i - t, // h = o.positions[a], // r = o.stagger, // p = s.positionIndex; // e ? (s.position < h && (h -= o.pathLength, p += o.itemsLength), r *= p - n) : (s.position >= h && (h += o.pathLength, s.positionIndex > n && (p -= o.itemsLength)), r *= n - p), // 0 === i && call(o.beginAll); // var c = { // index: i, // node: s.node, // selected: 0 === a, // unselected: 0 === s.positionIndex // }; // call(o.begin, c); // var d = { // position: s.position // }; // s.positionIndex = a, // s.position = o.getRealPosition(h), // o.animations.push({ // index: i, // anime: anime({ // targets: d, // position: h, // duration: o.duration, // easing: o.easing, // elasticity: o.elasticity, // delay: o.delay + r, // update: function() { // o.setPosition(s.node, d.position) // }, // complete: function() { // call(o.end, c), // o.animations = o.animations.filter(function(t) { // return t.index !== i // }), // 0 === o.animations.length && call(o.endAll) // } // }) // }) // } (a) // } // }, // selectPrevItem: function() { // this.selectItem(this.getPrevItem(this.currentIndex)) // }, // selectNextItem: function() { // this.selectItem(this.getNextItem(this.currentIndex)) // }, getPrevItem: function(t) { return t > 0 ? t - 1 : this.itemsLength - 1 }, getNextItem: function(t) { return t + 1 < this.itemsLength ? t + 1 : 0 }, getRealPosition: function(t) { var i = parseFloat(t); if (i < 0) for (; i < 0;) i += this.pathLength; else if (i >= this.pathLength) for (; i >= this.pathLength;) i -= this.pathLength; return i }, updateClass: function(t) { console.log(this.currentIndex) is.und(t) || (this.items[this.currentIndex].node.classList.remove("path-slider__current-item"), this.currentIndex = t), this.items[this.currentIndex].node.classList.add("path-slider__current-item") } }; var is = { arr: function(t) { return Array.isArray(t) }, str: function(t) { return "string" == typeof t }, fnc: function(t) { return "function" == typeof t }, und: function(t) { return "undefined" == typeof t } }; function getSinPath(options) { var _options = options || {}; var _width = _options.width || window.innerWidth; var _height = _options.height || window.innerHeight; var _addWidth = _options.addWidth || 100; var _controlSep = _options.controlSep || 50; var _curves = _options.curves || 2; var x = - _addWidth; var y = _height / 2; var amplitudeX = (_width + _addWidth * 1.3) / _curves; // X distance among curve points var amplitudeY = _height / 20; // Y distance between points and control points var path = []; path.push('M', x, y); var alternateY = true; var controlY; for (var i = 0; i < _curves; i++) { controlY = alternateY ? y - amplitudeY : y + amplitudeY; if (i === 0) { path.push('C', x + (amplitudeX / 2 - _controlSep), controlY); } else { path.push('S'); } path.push(x + (amplitudeX / 2 + _controlSep), controlY); path.push(x + amplitudeX, y); x += amplitudeX; alternateY = !alternateY; } return path.join(' '); } (function () { // Creating SVG and path elements and insert to DOM var svgNS = 'http://www.w3.org/2000/svg'; var svgEl = document.createElementNS(svgNS, 'svg'); // svgEl.setAttribute('viewBox',"0 0 2020 317"); var pathEl = document.createElementNS(svgNS, 'path'); // The `getSinPath` function return the `path` in String format pathEl.setAttribute('d', getSinPath()); pathEl.setAttribute('class', 'path-slider__path'); svgEl.appendChild(pathEl); document.getElementById('svg-add').appendChild(svgEl); // Changing `background-image` // Firstly, saving the computed `background` of each item, as these are defined in CSS // When item is selected, the `background` is set accordingly var items = document.querySelectorAll('.path-slider__item'); var images = []; for (var j = 0; j < items.length; j++) { images.push(getComputedStyle(items[j].querySelector('.item__circle')).getPropertyValue('background-image')); } var imgAnimation; var lastIndex; var setImage = function (index) { if (imgAnimation) { imgAnimation.pause(); sliderContainer.style['background-image'] = images[lastIndex]; sliderContainerBackground.style['opacity'] = 0; } lastIndex = index; sliderContainerBackground.style['background-image'] = images[index]; imgAnimation = anime({ targets: sliderContainerBackground, opacity: 1, easing: 'linear' }); }; // Adding the extra element needed to fade the images smoothly // Also set the image for the initial current item (the first one) var sliderContainer = document.querySelector('.path-slider'); var sliderContainerBackground = document.createElement('div'); sliderContainerBackground.setAttribute('class', 'path-slider__background'); setImage(0); sliderContainer.appendChild(sliderContainerBackground); // Initializing the slider var options = { startLength: 'center', paddingSeparation: 100, easing: 'easeOutCubic', begin: function (params) { // Item get selected, then set the `background` accordingly if (params.selected) { setImage(params.index); } } }; var slider = new PathSlider(pathEl, '.path-slider__item', options); // Regenerate the SVG `path` and update items position on `resize` event (responsive behavior) window.addEventListener('resize', function() { pathEl.setAttribute('d', getSinPath()); slider.updatePositions(); }); })();