UNPKG

guides

Version:

Simple way to highlighting DOM elements and guide your users with step-by-step welcome tours in your web app.

428 lines (379 loc) 13.9 kB
/* * guides 1.2.9 * Simple way to highlighting DOM elements and guide your users with step-by-step welcome tours in your web app. * https://github.com/ejulianova/guides * * Copyright 2015, Elena Petrova <elena.julianova@gmail.com> * Released under the MIT license. */ (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ 'use strict'; var Guides = require('./modules/Guides'); $.fn.guides = function (option, optionData) { return this.each(function () { var $this = $(this), data = $this.data('guides'), options = typeof option === 'object' && option; if (!data && typeof options == 'string') return; if (!data) $this.data('guides', (data = new Guides(this, options))); if (typeof option == 'string') data[option](optionData); }); }; $.guides = function (options) { return new Guides(null, options); }; $.fn.guides.Constructor = Guides; },{"./modules/Guides":3}],2:[function(require,module,exports){ var format = require('./format'); var Guide = function (guide, $container, options) { this.guide = guide; this._distance = guide.distance || options.distance; this._color = guide.color || options.color; this._class = guide.cssClass || options.cssClass || ''; this.$highlightedElement = $(guide.element).addClass('guides-current-element'); this.$container = $container; this.init(); }; Guide.prototype._arrowSize = 10; // Mx1,y1, Cdx1,dy1,dx2,dy2,x2,y2 // (x1,y1) - start point // (dx1,dy1) - curve control point 1 // (dx2,dy2) - curve control point 2 // (x2,y2) - end point Guide.prototype._path = 'M{0},{1} C{2},{3},{4},{5},{6},{7}'; Guide.prototype._arrowTemplate = '<svg width="{0}" height="{1}">\ <defs>\ <marker id="arrow" markerWidth="13" markerHeight="13" refX="2" refY="6" orient="auto">\ <path d="M2,1 L2,{3} L{3},6 L2,2" style="fill:{4};"></path>\ </marker>\ </defs>\ <path id="line" d="{2}" style="stroke:{4}; stroke-width: 1.25px; fill: none; marker-end: url(#arrow);"></path>\ </svg>'; Guide.prototype.init = function() { this.$guide = $('<div />', { 'class': 'guides-fade-in guides-guide ' + this._class, 'html': '<span>' + this.guide.html + '</span>' }); this._position(); return this; }; Guide.prototype._position = function () { if (this.$highlightedElement && this.$highlightedElement.length > 0) { this._attachToElement(); this.$guide.appendTo(this.$container); this._renderArrow(); } else { this._center(); } this._scrollIntoView(); }; Guide.prototype._center = function () { this.$guide .css('visibility', 'hidden') .appendTo(this.$container) .addClass('guides-center') .css({ left: 0, right: 0, textAlign: 'center', top: (window.innerHeight / 2) - (this.$guide.outerHeight() / 2) }).css('visibility', 'visible'); }; Guide.prototype._attachToElement = function () { var elOffset = this.$highlightedElement.offset(), docWidth = $('body').width(), docHeight = $('body').height(), leftSpace = elOffset.left, topSpace = elOffset.top, highlightedElementWidth = this.$highlightedElement.outerWidth(), highlightedElementHeight = this.$highlightedElement.outerHeight(), rightSpace = docWidth - leftSpace - highlightedElementWidth, bottomSpace = docHeight - topSpace - highlightedElementHeight, css = { color: this._color, top: docHeight / 2 > elOffset.top ? elOffset.top : 'auto', left: docWidth / 2 > elOffset.left ? elOffset.left : 'auto', right: docWidth / 2 > elOffset.left ? 'auto' : docWidth - elOffset.left - highlightedElementWidth, bottom: docHeight / 2 > elOffset.top ? 'auto' : elOffset.bottom }; switch (Math.max(leftSpace, rightSpace, topSpace, bottomSpace)) { case leftSpace: this.position = 'left'; css.paddingRight = this._distance; css.right = $(document).width() - elOffset.left; css.left = 'auto'; break; case topSpace: this.position = 'top'; css.paddingBottom = this._distance; css.bottom = $(document).height() - elOffset.top; css.top = 'auto'; break; case rightSpace: this.position = 'right'; css.paddingLeft = this._distance; css.left = elOffset.left + highlightedElementWidth; css.right = 'auto'; break; default: this.position = 'bottom'; css.paddingTop = this._distance; css.top = elOffset.top + highlightedElementHeight; css.bottom = 'auto'; break; } this.$guide.addClass('guides-' + this.position).css(css); }; Guide.prototype._renderArrow = function() { this._width = this.$guide.outerWidth(); this._height = this.$guide.outerHeight(); this.$guide.append(format(this._arrowTemplate, this._width, this._distance, this[this.position](), this._arrowSize, this._color)); }; Guide.prototype.top = function () { var coord = this._verticalAlign(); return this._getPath(coord); }; Guide.prototype.bottom = function () { var coord = this._verticalAlign(true); return this._getPath(coord); }; Guide.prototype.left = function () { var coord = this._horizontalAlign(); return this._getPath(coord); }; Guide.prototype.right = function () { var coord = this._horizontalAlign(true); return this._getPath(coord); }; Guide.prototype._getPath = function (coord) { return format(this._path, coord.x1, coord.y1, coord.dx1, coord.dy1, coord.dx2, coord.dy2, coord.x2, coord.y2); }; Guide.prototype._getFluctuation = function () { return Math.floor(Math.random() * 20) + 10; }; Guide.prototype._verticalAlign = function (bottom) { var x1 = this._width / 2, y1 = bottom ? this._distance : 0, x2 = Math.max( Math.min( this.$highlightedElement.offset().left + (this.$highlightedElement.outerWidth() / 2) - this.$guide.offset().left, this._width - this._arrowSize), this._arrowSize), y2 = bottom ? this._arrowSize : this._distance - this._arrowSize; return { x1: x1, y1: y1, x2: x2, y2: y2, dx1: Math.max(0, Math.min(Math.abs((2 * x1) - x2) / 3, this._width) + this._getFluctuation()), dy1: bottom ? Math.max(0, y2 + (Math.abs(y1 - y2) * 3 / 4)) : Math.max(0, y1 + (Math.abs(y1 - y2) * 3 / 4)), dx2: Math.max(0, Math.min(Math.abs(x1 - (x2 * 3)) / 2, this._width) - this._getFluctuation()), dy2: bottom ? Math.max(0, y2 + (Math.abs(y1 - y2) * 3 / 4)) : Math.max(0, y1 + (Math.abs(y1 - y2) * 3 / 4)) } }; Guide.prototype._horizontalAlign = function (right) { var x1 = right ? this._distance : this._width - this._distance, y1 = this._height / 2, x2 = right ? this._arrowSize : this._width - this._arrowSize, y2 = Math.max( Math.min( this.$highlightedElement.offset().top + (this.$highlightedElement.outerHeight() / 2) - this.$guide.offset().top, this._height - this._arrowSize), this._arrowSize); return { x1: x1, y1: y1, x2: x2, y2: y2, dx1: right ? Math.max(0, x2 + (Math.abs(x1 - x2) * 3 / 4)) : Math.max(0, x1 + Math.abs(x1 - x2) * 3 / 4), dy1: Math.max(0, Math.min(Math.abs((2 * y1) - y2) / 3, this._height) + this._getFluctuation()), dx2: right ? Math.max(0, x2 + (Math.abs(x1 - x2) * 3 / 4)) : Math.max(0, x1 + Math.abs(x1 - x2) * 3 / 4), dy2: Math.max(0, Math.min(Math.abs(y1 - (y2 * 3)) / 2, this._height) + this._getFluctuation()) } }; Guide.prototype._scrollIntoView = function () { if (this.$highlightedElement.length === 0) { $('html,body').animate({ scrollTop: 0 }, 500); return; } var guideOffset = this.$guide.offset(), top = guideOffset.top, bottom = guideOffset.top + this.$guide.outerHeight(), left = guideOffset.left, right = guideOffset.left + this.$guide.outerWidth(), scrollTop = $(document).scrollTop(), scrollLeft = $(document).scrollLeft(); //scroll vertically to element if it is not visible in the view port if (scrollTop > top || scrollTop + $(window).height() < bottom) { $('html,body').animate({ scrollTop: this.position === 'bottom' ? top - 100 : top }, 500); } //scroll horizontally to element if it is not visible in the view port if (scrollLeft > left || scrollLeft + $(window).width() < right) { $('html,body').animate({ scrollLeft: this.position === 'righ' ? left - 100 : left }, 500); } }; Guide.prototype.destroy = function() { this.$highlightedElement.removeClass('guides-current-element'); this.$guide.remove(); }; module.exports = Guide; },{"./format":4}],3:[function(require,module,exports){ var Guide = require('./Guide'); var Guides = function (element, options) { this.element = element; this.$element = $(element); this.options = {}; this._current = 0; this.setOptions(options); if (element) { this.$element.on('click.guides', $.proxy(this.start, this)); } }; Guides.DEFAULTS = { distance: 100, color: '#fff', cssClass: '', guides: [] }; Guides.prototype.start = function(e) { if (e) { e.preventDefault(); } if (this._isAlreadyRunning()) { return this; } this._current = 0; this._renderCanvas() ._renderGuide(this.options.guides[this._current]) ._callback('start'); return this; }; Guides.prototype.end = function() { if (this.$canvas) { this.$canvas.remove(); this.$canvas = null; } if (this._currentGuide) { this._currentGuide.destroy(); this._currentGuide = null; } $(document).off('keyup.guides'); this._callback('end'); return this; }; Guides.prototype.next = function () { this._renderGuide(this.options.guides[++this._current]) ._callback('next'); return this; }; Guides.prototype.prev = function () { if (!this._current) { return; } this._renderGuide(this.options.guides[--this._current]) ._callback('prev'); return this; }; Guides.prototype.setOptions = function(options) { if (typeof options !== 'object') { return this; } this.options = $.extend({}, Guides.DEFAULTS, this.options, options); }; Guides.prototype.destroy = function() { this.end(); this.$element.off('click.guides'); this._callback('destroy'); return this; }; Guides.prototype._callback = function (eventName) { var callback = this.options[eventName], eventObject = { sender: this }; if (this._currentGuide) { eventObject.$element = this._currentGuide.guide.element; eventObject.$guide = this._currentGuide.$element; } if ($.isFunction(callback)) { callback.apply(this, [eventObject]); } }; Guides.prototype._isAlreadyRunning = function () { return !!this.$canvas; }; Guides.prototype._renderCanvas = function () { this.$canvas = $('<div />', { 'class': 'guides-canvas guides-fade-in', 'html': '<div class="guides-overlay"></div><div class="guides-mask"></div>' }).appendTo('body'); this._bindNavigation(); return this; }; Guides.prototype._renderGuide = function (guide) { if (!guide) { //no more guides this.end(); return this; } if (this._currentGuide) { this._currentGuide.destroy(); } this._callback('render', guide); if ($.isFunction(guide.render)) { guide.render.apply(this, [guide]); } this._currentGuide = new Guide(guide, this.$canvas, this.options); return this; }; Guides.prototype._bindNavigation = function () { this.$canvas.on('click.guides', $.proxy(this._onCanvasClick, this)); $(document).on('keyup.guides', $.proxy(this._onDocKeyUp, this)); return this; }; Guides.prototype._onCanvasClick = function (e) { this.next(); }; Guides.prototype._onDocKeyUp = function (e) { switch (e.which) { case 27: //esc this.end(); break; case 39: //right arrow case 32: //space this.next(); break; case 37: //left arrow case 8: //backspace e.preventDefault(); this.prev(); break; default: break; } }; module.exports = Guides; },{"./Guide":2}],4:[function(require,module,exports){ module.exports = function format () { var s = arguments[0]; for (var i = 0; i < arguments.length - 1; i++) { var reg = new RegExp("\\{" + i + "\\}", "gm"); s = s.replace(reg, arguments[i + 1]); } return s; }; },{}]},{},[1])