7

為第一次使用網頁顯示「新手提示」之懶人工具

 3 years ago
source link: https://blog.darkthread.net/blog/simple-novice-guide/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

為第一次使用網頁顯示「新手提示」之懶人工具

2021-04-01 10:25 PM 1 1,574

寫網頁的人總夢想著自己寫的介面夠簡單夠直覺,不需說明文件,使用者模索兩下就能上手。但事與願違,網頁上一些自以為夠明顯一定會被使用者發現的得意設計,上線半年還乏人問津,常令設計者一陣鼻酸。

手機 App 有一種不錯的設計概念可供借鏡,做法是第一次開啟 App 時先跳出一段動畫展示,簡要提示新功能或操作技巧,使用者必須看完才能開啟使用,而它只有在首次使用時顯示,之後便不再出現。

我想把這種「新手提示」的概念應用在網頁上 - 第一次開啟網頁時先放一層遮罩擋住 UI 不讓使用者操作,以動畫方式提示操作訣竅,展示結束後在 localStorage 留下已讀註記,下回再開啟時檢查到已讀註記便不再顯示。

為每個網頁大費周章客製專屬新手提示動畫有點辛苦,於是我想到用 jQuery Selector 找在特定元素在旁邊附上一小段文字說明的點子,這樣子用 { "selector1": "說明文字1", "selector2": "說明文字2" ... } 定義出標註的元素及文字,呼呼共用函式播放,如此只需簡單設定就能為網頁加上新手提示了。 這種新手指示的效果當然比不上請視覺設計人製作的精美動畫相比,但比起靜態說明文件或任由使用者自己摸索,已是了不起的大躍進,可以大幅提供企業內部應用系統的操作體驗。

最後我把這個概念寫成共用函式,還加入自訂說明文字位置、自動捲動至元素位置、顯示前後自訂事件... 等功能,並小心翼翼保持 IE 相容,理論上可滿足大部分應用情境。而為了實地展示,我 Fork 了一個網頁欄位驗證 Github 專案當成示範對象,在網頁加入以下程式碼:

<script src="afet.novice-guide.js"></script>
<script>
	afet.ShowNoviceGuide('readFlagName', false, {
		'h1': {
			offsetLeft: 0, offsetTop: -20,
			text: '<div style="padding:12px;width:500px;text-align:center;font-size:2em;color:yellow">\
				簡易式「新手提示」展示</div>'
		},
		'[name=name]': '請在這裡填入您的姓名\n例如:黑鮪魚之夢',
		'[type=checkbox]:eq(1)~span': {
			offsetLeft: -50, offsetTop: 20,
			before: function (el) { el.closest('label').css('border', '1px solid red'); },
			after: function (el) { el.closest('label').css('border', "none"); },
			text: '點選文字也可打勾哦'
		},
		'[type=submit]': {
			offsetLeft: 0, offsetTop: -50,
			text: '按這裡送出表單'
		},
		'.email': {
			offsetLeft: 40, offsetTop: 20,
			before: function (el) { validator.checkField(el[0]); },
			after: function (el) { validator.reset(); },
			text: '檢核失敗將出現紅色警語'
		},
		'.reshow': '新手提示只會顯示一次\n重看請按這裡清localStorage旗標\n並重新整理頁面'
	});
</script>

呈現效果如下,想實地動手的朋友可玩玩線上版

函式庫不大,不到 120 行打發,單一 js 搞定,有 jQuery 就能動。完整程式碼如下:

var afet;
(function (afet) {
	/**
	 * 簡易新手提示產生器 Ver 1.0.0 by Jeffrey https://blog.darkthread.net
	 * @param {any} guideCode 指示代碼,首次顯示後在localStorage下註記以免重複出現
	 * @param {boolean} force 無視 localStorage 已讀註記,一律顯示
	 * @param {any} instructions Key/Value 形式,Key為 Selector,Value 為說明物件
	 * @param {any} instrucStyles 說明區塊自訂樣式
	 * 說明物件格式為 { offsetTop: y, offsetLeft: x, text: '...', before: function(el) {...}, after: function(el) {...}  }
	 * 說明物件也可以字串取代,則說明會寫示在選取元素下方,說明文字以可為 HTML 或純文字,若為純文字支援換行顯示
	  */
	function showNoviceGuide(guideCode, force, instructions, instrucStyles) {
		var zIdx = 9990, bgColor = '#2196f3', key = "$NG_" + guideCode;
		if (localStorage.getItem(key) && !force) return;
		var docW = $(document).width() + 'px', docH = $(document).height() + 'px';
		var mask = $('<div></div>').css({
			position: 'absolute', top: 0, left: 0, width: docW, height: docH, 'z-index': zIdx,
			opacity: 0.2, 'background-color': '#444', cursor: 'pointer'
		});
		mask.appendTo('body');
		function getNumber(s) { return parseInt(s.replace('px', '')); }
		var queue = Object.keys(instructions);
		var lastTip;
		var svg = $('<svg version="1.1"></svg>').css({ position: 'absolute', top: 0, left: 0, zIndex: zIdx + 1, width: docW, height: docH, opacity: 0.5, cursor: 'pointer' });
		svg.appendTo('body');
		//http://chubao4ever.github.io/tech/2015/07/16/jquerys-append-not-working-with-svg-element.html
		function SVG(tag) {
			return document.createElementNS('http://www.w3.org/2000/svg', tag);
		}
		function drawLine(x1, y1, x2, y2, color) {
			$(SVG('line'))
				.attr('x1', x1).attr('y1', y1).attr('x2', x2).attr('y2', y2)
				.attr('stroke', color).attr('stroke-width', '2px')
				.appendTo(svg);
		}
		function drawCircle(x, y, r, color) {
			$(SVG('circle')).attr('cx', x).attr('cy', y).attr('r', r).attr('fill', color)
				.appendTo(svg);
		}
		svg.click(function () {
			if (lastTip) {
				 lastTip.trigger('next').remove();
				 lastTip = null;
			}
			svg.empty();
			if (!queue.length) {
				mask.remove();
				svg.remove();
				window.scrollTo(0, 0);
				localStorage.setItem(key, "Y");
				return;
			}
			var sel = queue.shift();
			var focusElem = $(sel);
			var instruction = instructions[sel];
			if (typeof instruction === "string") {
				instruction = {
					offsetTop: focusElem.height() + 20,
					offsetLeft: 0,
					text: instruction
				};
			}
			if (focusElem.length && focusElem.is(":visible")) {
				$.isFunction(instruction.before) && instruction.before(focusElem);
				var pos = focusElem.offset();
				var tip = $('<div></div>');
				tip.css({
					position: 'absolute',
					top: (pos.top + instruction.offsetTop) + 'px',
					left: (pos.left + instruction.offsetLeft) + 'px',
					"z-index": zIdx + 2,
					opacity: 1,
					backgroundColor: bgColor,
					padding: '6px',
					color: 'white'
				});
				if (instrucStyles) tip.css(instrucStyles);
				var instrText = instruction.text;
				if (instrText.indexOf('<') === 0)
					tip.html(instrText);
				else {
					tip.text(instrText);
					if (instrText.indexOf('\n') > -1)
						tip.html(tip.html().replace(/\n/g, '<br />'));
				}
				var s = getComputedStyle(focusElem[0]);
				tip.attr('data-st', (pos.left + getNumber(s.paddingLeft) + focusElem.width() / 2) + "," 
					+ (pos.top + getNumber(s.paddingTop) + focusElem.height() / 2));
				$.isFunction(instruction.after) && tip.bind("next", function() { 
					instruction.after(focusElem);
				});
			}
			else {
				//if not found, trigger click event to show next tip
				setTimeout(function () { svg.click(); }, 0);
				return;
			}
			lastTip = tip;
			lastTip.appendTo('body');
			var pos = lastTip.offset();
			var st = lastTip.attr("data-st").split(',');
			drawCircle(st[0], st[1], 5, bgColor);
			drawLine(st[0], st[1], pos.left + lastTip.width() / 2, pos.top + lastTip.height() / 2, bgColor);
			var y = st[1] - $(document).scrollTop()
			console.log(y);
			if (y > $(window).height() / 2)	window.scrollTo(0, st[1]);
			else if (y < 30) window.scrollTo(0, 0);
			svg.hide().fadeIn('slow');
			lastTip.hide().fadeIn('slow');
		});
		setTimeout(function () { svg.click(); }, 500);
	}
	afet.ShowNoviceGuide = showNoviceGuide;
})(afet || (afet = {}));

實際套用在幾個網頁上效果不錯,是個實用的好工具。好久沒寫前端程式,這個算是近期的得意之作,野人獻曝分享給大家~

Demostration of how to create simple novice guide for first time webpage user with shared JavaScript functions.


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK