浏览器渲染页面的方式各不相同,甚至同一浏览器的不同版本(“杰出代表”是 IE)也有差异。因此,浏览器兼容成为前端开发人员的必备技能。如果有一份浏览器 Hack 手册,那查询起来就方便多了。这篇文章就向大家分享 Browserhacks 帮我们从网络上收集的各个浏览器特定的 CSS & JavaScript Hack,记得推荐和分享啊!…
如何用网页脚本追踪用户, JavaScript实现用户行为跟踪收集, js 分析客户行为, CSS 来追踪用户
本文介绍如何编写 JavaScript 脚本,将用户数据发回服务器。
我做了一个代码仓库,包含了下面所有的例子,可以运行查看效果。
一、同步 AJAX
数据发回服务器的常见做法是,将收集好的用户数据,放在unload
事件里面,用 AJAX 请求发回服务器。
但是,异步 AJAX 在unload
事件里面不一定能成功,因为网页已经处于卸载中,浏览器可能发送,也可能不发送。所以,要改成同步 AJAX 请求。
window.addEventListener('unload', function (event) { let xhr = new XMLHttpRequest(); xhr.open('post', '/log', false); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send('foo=bar'); });
上面代码中,xhr.open()
方法的第三个参数是false
,表示同步请求。
这种方法最大的问题在于,浏览器逐步将不允许在主线程上面,使用同步 AJAX。所以,上面代码实际上不能用。
二、异步 AJAX
异步 AJAX 其实是能用的。前提是unload
事件里面,必须有一些很耗时的同步操作。这样就能留出足够的时间,保证异步 AJAX 能够发送成功。
function log() { let xhr = new XMLHttpRequest(); xhr.open('post', '/log', true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send('foo=bar'); } window.addEventListener('unload', function(event) { log(); // a time-consuming operation for (let i = 1; i < 10000; i++) { for (let m = 1; m < 10000; m++) { continue; } } });
上面代码中,强制执行了一次双重循环,拖长了unload
事件的执行时间,导致异步 AJAX 能够发送成功。
三、追踪用户点击
setTimeout
也能拖延页面卸载,保证异步请求发送成功。下面是一个例子,追踪用户点击。
// HTML 代码如下 // <a id="target" href="https://baidu.com">click</a> const clickTime = 350; const theLink = document.getElementById('target'); function log() { let xhr = new XMLHttpRequest(); xhr.open('post', '/log', true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send('foo=bar'); } theLink.addEventListener('click', function (event) { event.preventDefault(); log(); setTimeout(function () { window.location.href = theLink.getAttribute('href'); }, clickTime); });
上面代码使用setTimeout
,拖延了350毫秒,才让页面跳转,因此使得异步 AJAX 有时间发出。
四、反弹追踪
追踪用户点击,还可以使用反弹追踪(bounce tracking)。
所谓”反弹追踪”,就是网页跳转时,先跳到一个或多个中间网址,以便收集信息,然后再跳转到原来的目标网址。
// HTML 代码如下 // <a id="target" href="https://baidu.com">click</a> const theLink = document.getElementById('target'); theLink.addEventListener('click', function (event) { event.preventDefault(); window.location.href = '/jump?url=' + encodeURIComponent(theLink.getAttribute('href')); });
上面代码中,用户点击的时候,会强制跳到一个中间网址,将信息携带过去,处理完毕以后,再跳到原始的目标网址。
谷歌和百度现在都是这样做,点击搜索结果时,会反弹多次,才跳到目标网址。
五、Beacon API
上面这些做法,都会延缓网页卸载,严重影响用户体验。
为了解决网页卸载时,异步请求无法成功的问题,浏览器特别实现了一个 Beacon API,允许异步请求脱离当前主线程,放到浏览器进程里面发出,这样可以保证一定能发出。
window.addEventListener('unload', function (event) { navigator.sendBeacon('/log', 'foo=bar'); });
上面代码中,navigator.sendBeacon()
方法可以保证,异步请求一定会发出。第一个参数是请求的网址,第二个参数是发送的数据。
注意,Beacon API 发出的是 POST 请求。
六、ping 属性
HTML 的<a>
标签有一个ping
属性,只要用户点击,就会向该属性指定的网址,发出一个 POST 请求。
<a href="https://baidu.com" ping="/log?foo=bar"> click </a>
上面代码中,用户点击跳转时,会向/log
这个网址发一个 POST 请求。
ping
属性无法指定数据体,似乎只能通过 URL 的查询字符串携带信息。
七、参考链接
- Link Click Analytics and Privacy, John Wilander
- ping Attribute, David Walsh
八、实例
1、收集基础数据
基础数据涵盖
(1) 业务类:业务流页面地址、用户停留时间、打开次数、
会话ID、客户端IP、业务流步骤等
(2) 辅助类:浏览器、OS、COOKIE等
(3) 示例
/* 记录构造基础数据 */ var Logger = { Debug: false, UserControl: 'inputUser', HistoryControl: 'inputHistory', TimerControl: 'inputTimer', GetUser: function() { if (!$f(this.UserControl)) return ",,"; return $f(this.UserControl).value; }, /*-- attribute --*/ Guid: function() { return this.GetUser().split(',')[0]; }, SessionID: function() { return this.GetUser().split(',')[1]; }, GetStep: function() { return this.GetUser().split(',')[2]; }, ProcessTime: function() { if (!$f(this.TimerControl)) return "0"; return $f(this.TimerControl).value; }, AppSys: 1, //不同系统编号 Environment: '', //Environment.Dev:开发Dev、测试Test、正式Official IsNewSite: 1, //是否历史返回 IsHistory: function() { if (!$f(this.HistoryControl)) return false; if ($f(this.HistoryControl).value.length > 0) return true; //if (this.IsSuccReturn()) return true; //成功返回 非历史 return false; }, IsStep2History: function() { //是否为第2步返回历史 if (!$f(this.HistoryControl)) return false; var history = $f(this.HistoryControl).value; if (history.length == 0) return false; if (history.split(',').length > 1) return true; return false; }, //是否为页面reload返回 IsReturn: function() { if (typeof getUrlParam != "function") return false; var para = getUrlParam("return"); if (para == "1") return true; return false; }, //是否成功返回 IsSuccReturn: function() { var para = getUrlParam("succret"); if (para == "1") return true; return false; }, //tracetype,guid,sessionid,processtime,description WriteStepLog: function() { var argc = arguments.length; var traceType = (argc > 0) ? arguments[0] : ""; var guid = (argc > 1) ? arguments[1] : ""; var sessionID = (argc > 2) ? arguments[2] : ""; var processTime = (argc > 3) ? (arguments[3] == "" ? "0" : arguments[3]) : "0"; var description = (argc > 4) ? arguments[4] : ""; var url = (argc > 5) ? arguments[5] : ""; /*with (Trace.Parameter) { TraceType = traceType; Guid = guid; SessionID = sessionID; PageUrl = window.location.href; ProcessTime = processTime; //set const value AppSys = 1; Environment = Environment.Dev; //Offical IsNewSite = 1; Description = description; }*/ Trace.Parameter.TraceType = traceType; Trace.Parameter.Guid = guid; Trace.Parameter.SessionID = sessionID; if (url.length == 0) { url = window.location.href; //alert("self:" + window.location.href + ",refer:" + self.document.referrer); if (url.toLowerCase().indexOf('errorpage.aspx') > -1 && traceType.indexOf('ret') == -1) { if (document.referrer != null && document.referrer != "") url = document.referrer; } } Trace.Parameter.PageUrl = url; Trace.Parameter.ProcessTime = processTime; Trace.Parameter.AppSys = this.AppSys; if (this.Environment.length == 0) this.Environment = Environment.Official; var curUrl = window.location.href.toLowerCase(); if (curUrl.indexOf('https://') > -1) { this.Environment = this.Environment.replace('http://', 'https://'); } Trace.Parameter.Environment = this.Environment; Trace.Parameter.IsNewSite = this.IsNewSite; Trace.Parameter.Description = escape(description); if (this.Debug) { alert(Trace.Parameter.TraceType + "," + Trace.Parameter.Guid + "," + Trace.Parameter.SessionID + "," + Trace.Parameter.ProcessTime + "," + Trace.Parameter.Description); } Trace.Submit(Trace.Parameter, null, 'img'); }, WriteOpenLog: function() { try { var argc = arguments.length; var step = (argc > 0) ? arguments[0] : ""; var desc = (argc > 1) ? arguments[1] : ""; if (typeof PTID != "undefined" && PTID.length > 0) { desc += ",PTID:" + PTID; } var loginstep = this.GetStep(); /*if (this.IsSuccReturn()) { //成功返回 Logger.WriteStepLog(Step.succret, this.Guid(), this.SessionID(), this.ProcessTime(), desc); this.SetTimer(); }*/ if (step == "step1" && !this.IsHistory() && typeof loginstep != "undefined" && loginstep.length > 0) { //登录返回(第一步发生) Logger.WriteStepLog(loginstep, this.Guid(), this.SessionID(), this.ProcessTime(), desc); } else if (step == "step1" && !this.IsHistory() && !this.IsReturn()) //not history back,not page reload { Logger.WriteStepLog(step, this.Guid(), this.SessionID(), this.ProcessTime(), desc); } else if ((step == "step2" && !this.IsStep2History()) || step == "step3") { //第2步、第3步 Logger.WriteStepLog(step, this.Guid(), this.SessionID(), this.ProcessTime(), desc); this.SetTimer(); } else if (step == "password" || step == "mobile" || step == "cancelbind") { //一点充没有历史返回等属性 Logger.WriteStepLog(step, this.Guid(), this.SessionID(), this.ProcessTime(), desc); this.SetTimer(); } else if (this.IsHistory() || this.IsStep2History() || this.IsReturn()) { //历史返回 Logger.WriteStepLog(step + "ret", this.Guid(), this.SessionID(), "0", desc); this.SetTimer(); } else { Logger.WriteStepLog(step, this.Guid(), this.SessionID(), "0", desc); } } catch (e) { } }, WriteSubmitLog: function() { try { var argc = arguments.length; var step = (argc > 0) ? arguments[0] : ""; var desc = (argc > 1) ? arguments[1] : ""; var url = (argc > 2) ? arguments[2] : ""; if (typeof PTID != "undefined" && PTID.length > 0) { desc += ",PTID:" + PTID; } Logger.WriteStepLog(step, this.Guid(), this.SessionID(), this.ProcessTime(), desc, url); $f(this.HistoryControl).value = "1"; //set step2 if (step == "step2submit") { $f(this.HistoryControl).value = "1,1"; } this.SetTimer(); } catch (e) { } }, SetTimer: function() { //reset timer if (Timer && typeof Timer != "undefined") { Timer.Reset(); } }, DirectOpenLog: function() { try { var argc = arguments.length; var step = (argc > 0) ? arguments[0] : ""; var desc = (argc > 1) ? arguments[1] : ""; if (typeof PTID != "undefined" && PTID.length > 0) { desc += ",PTID:" + PTID; } this.AppSys = 2; Logger.WriteStepLog(step, this.Guid(), this.SessionID(), this.ProcessTime(), desc); if (step != Step.step1) { this.SetTimer(); } } catch (e) { } } }; var $f = function(name) { return document.getElementById(name); } //记录客户端脚本错误 window.onerror = function GetErrors(error) { try { var msg; for (var i = 0; i < arguments.length; i++) { if (i == 0 || i == 2) { msg += " | " + arguments[i]; } } if (msg.length > 0 && typeof Logger != 'undefined') { Logger.WriteStepLog('syserror', '-', '-', '', msg); } window.onerror = null; return true; } catch (e) { }; }
2、时间统计
通过在页面放置计时器,计算当前视图下用户的停留时间。
示例:
/*--------Timer Script------------- * * 页面计时器控件 * 1、Timer.BindControl = 'inputTimer'; * 2、<input id="inputTimer" type="hidden" class="timer" /> * 不写Cookie、不显示定时器时,采用(EndTime - StratTime)即可 */ var up, down; var cmin1, csec1, clock; var Timer = { Debug: false, BindControl: 'inputTimer', StartTime: '', EndTime: '', StartTimer: function() { if (!$f(this.BindControl)) return; if ($f(this.BindControl).value != "") return; //$("#" + this.BindControl).val(""); cmin1 = 0; csec1 = 0; //每个页面单独记录,不需要采用Cookie,屏蔽 // var cookie = GetCookie("Timer"); // if (cookie) { // cmin1 = parseInt(this.Minutes(cookie)); // csec1 = parseInt(this.Seconds(cookie)); // DeleteCookie("Timer"); // } // else { // cmin1 = csec1 = 0; // } this.Repeat(); }, SetValue: function() { var html = $f(this.BindControl).value; if (html != null && html.length > 0) SetCookie("Timer", html); }, Minutes: function(data) { for (var i = 0; i < data.length; i++) if (data.substring(i, i + 1) == ":") break; return (data.substring(0, i)); }, Seconds: function(data) { for (var i = 0; i < data.length; i++) if (data.substring(i, i + 1) == ":") break; return (data.substring(i + 1, data.length)); }, Display: function(min, sec) { var disp = ""; if (min <= 9) disp += "0" + min + ":"; else disp += min + ":"; if (sec <= 9) disp += "0" + sec; else disp += sec; return (disp); }, Repeat: function() { csec1++; if (csec1 == 60) { csec1 = 0; cmin1++; } $f(this.BindControl).value = this.Display(cmin1, csec1); if (this.Debug) $f("inputDebug").value = this.Display(cmin1, csec1); clock = window.setTimeout(function() { Timer.Repeat() }, 1000); }, //重新开始计时 Reset: function() { $f(this.BindControl).value = ""; window.clearTimeout(clock); Timer.StartTimer(); }, AddTrigger: function() { var list = document.getElementsByTagName("INPUT"); for (var i = 0; i < list.length; i++) { if (list[i].type.toUpperCase() == 'TEXT') { if (list[i].addEventListener) { list[i].addEventListener("keyup", function() { Timer.StartTimer(); }, false); } else { list[i].attachEvent("onkeyup", function() { Timer.StartTimer(); }); } } } } }; if (document.all) { window.attachEvent("onload", function() { Timer.AddTrigger() }); } else { window.addEventListener("load", function() { Timer.AddTrigger() }, false); } if (Timer.Debug) { if (!document.getElementById("inputDebug")) { document.write("<input type='text' id='inputDebug' />"); } } /* 兼容两种模式设定Cookie */ //$(window).unload(function() { Timer.SetValue(); }); //$("form").submit(function() { Timer.SetValue(); });
3、异步记录
将收集到的数据通过GET、POST异步的方式发送到目标服务器
示例:
/*--------Trace Script-------------*/ var Step = { /* step */ step1: "step1", //第一步打开,不含历史返回、成功返回 step2: "step2", step3: "step3", /* post */ step1submit: "step1submit", //第一步提交 step2submit: "step2submit", step3submit: "step3submit", step3resubmit: "step3resubmit", //第三步重新提交 /* success */ success:"success", //操作成功 succret: "succret", //成功返回 step1ret: "step1ret", //返回第一步 step2ret: "step2ret", step3ret: "step3ret", /* error */ error:"error", //操作失败 errstep1: "errstep1", //第一步出错 错误页面记录 errstep2: "errstep2", errstep3: "errstep3", errstep1ret: "errstep1ret", //出错页返回到第一步 errstep2ret: "errstep2ret", errstep3ret: "errstep3ret", /* login */ loginb1: "loginb1", //银行卡登录返回 loginc1: "loginc1", //实物卡登录返回 loginbind: "loginbind", //一点充登录返回 step1login: "step1login", //第一步登录界面 /* other */ bind: "bind", //用户绑定一点充 mobile: "mobile", //修改手机号 password: "password", //修改密码 cancelbind: "cancelbind", //取消服务 Login: "Login", //用户登录日志 querydeposit: "querydeposit", //充值记录 querycardbalance: "querycardbalance", //实物卡余额 clickkf: "clickkf", //点击在线客服 closekf: "closekf", //关闭在线客服 clickaccount1: "clickaccount1", //点击新增常用账号 clickaccount2: "clickaccount2" //点击新增确定按钮 }; var Environment = { Dev: "http://dev.xxx.com", Test: "http://test.xxx.com", Official: "http://www.xxx.com" } var Trace = { AutoSubmit: false, //是否在提交表单时自动处理 Parameter: { TraceType: '', //TraceType.open Guid: '0', SessionID: '', PageUrl: '', Description: '', ProcessTime: '', IsNewSite: false, AppSys: 1, ClientIP: '', Environment: Environment.Official, Extend: {} }, MyAjax: function() { this.xml = false; this.GetXmlHttp = function() { if (!this.xml && typeof XMLHttpRequest != 'undefined') { this.xml = new XMLHttpRequest(); } else { var MSXML = ['MSXML2.XMLHTTP.5.0', 'MSXML2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP']; for (var i = 0; i < MSXML.length; i++) { try { this.xml = new ActiveXObject(MSXML[i]); break; } catch (e) {//alert(e.message); } } } } this.GetXmlHttp(); var xmlHttp = this.xml; var ajax = this; var callBack = null; this.updatePage = function() { if (xmlHttp.readyState == 4) { var response = xmlHttp.responseText; if (callBack != null && typeof callBack == "function") { callBack(response); } } } this.toQueryString = function(json) { var query = ""; if (json != null) { for (var param in json) { query += param + "=" + escape(json[param]) + "&" } } return query; } //提交参数,回调函数, post、get方法 this.invoke = function(params, pageCallBack, method) { if (xmlHttp) { var query = ""; query += this.toQueryString(params); query = query.substring(0, query.length - 1); //var thisReg = new RegExp(/'|"/gi); //query = query.replace(thisReg, ""); callBack = pageCallBack; if (method != null && method.toUpperCase() == "GET") { var url = params.Environment + "/Trace.aspx?" + query; xmlHttp.onreadystatechange = ajax.updatePage; xmlHttp.open("GET", url, true); xmlHttp.setRequestHeader("TraceAjax-Ver", "ver1.0"); xmlHttp.send(null); } else if (method != null && method.toUpperCase() == "POST") { var url = params.Environment + "/Trace.aspx"; // xmlHttp.setRequestHeader("Content-Length",query); xmlHttp.onreadystatechange = ajax.updatePage; //new CallClient(this); xmlHttp.open("POST", url, true); xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xmlHttp.setRequestHeader("TraceAjax-Ver", "ver1.0"); xmlHttp.send(query); } else { //跨域 Trace.CreateTraceImg(); document.getElementById("traceImg").src = params.Environment + "/Trace.aspx?a=" + Math.random(1000000000) + "&" + query; } } } }, CreateTraceImg: function() { if (!document.getElementById("traceImg")) { document.write("<img id=\"traceImg\" style='display:none' />"); // var imgNode = document.createElement("img") // imgNode.setAttribute("id", "traceImg") // imgNode.style.display = "none"; // document.body.appendChild(imgNode); } }, Submit: function(params, pageCallBack, method) { try { var ajax = new Trace.MyAjax(); //校验step是否正确 // for (var i = 0; i < Step.length; i++) { // if (params.TraceType == Step[i]) { // alert(Step[i]); // } // } ajax.invoke(params, pageCallBack, method); } catch (e) { } } }; Trace.CreateTraceImg();
4、后端数据分析
结合前台收集的用户数据,后台初步可以完成,每一个页面的打开次数、占用时长,提交率、以及后台业务流的成功率等,通过报表形式展示数据分析界面,这样就可以监控用户行为异常、监控系统波动以及影响区域。
5、扩展
通过记录来源地址,可以更详尽的分析用户行为的流程。
通过追加页面元素的点击情况,从而计算点击率。
虽然服务器端也可以做到这些过程,但需求在不同的过程中插入众多的日志记录,进而POST到服务器端,过于繁冗!
另外跟踪服务器可对来源的数据请求验证其合法性,以及是否授权。
*注意:在Tracer的过程中,如遇采用系统方法如:绑定事件window.onload,需要注意不用覆盖了方法,避免与应用的页面冲突,可以通过来添加事件监听,eg:
AddTrigger: function() { var list = document.getElementsByTagName("INPUT"); for (var i = 0; i < list.length; i++) { if (list[i].type.toUpperCase() == 'TEXT') { if (list[i].addEventListener) { list[i].addEventListener("keyup", function() { Timer.StartTimer(); }, false); } else { list[i].attachEvent("onkeyup", function() { Timer.StartTimer(); }); } } } }
6、CSS 来追踪用户
除了使用 JS 追踪用户,现在有人提出了还可以使用 CSS 进行网页追踪和分析,译者认为,这种方式更为 优雅,更为 简洁,且 不好屏蔽,值得尝试一波,了解更多,可查看 仓库地址(https://github.com/jbtronics/CrookedStyleSheets) 和 demo(http://crookedss.bplaced.net/)
我们可以用它来做什么
我们可以收集关于用户的一些基本信息,例如 屏幕分辨率(当浏览器最大化时)以及用户使用的什么浏览器(引擎)
此外,我们可以监测用户是否点击某个链接或鼠标悬停在某个元素上,用来 追踪用户悬停的链接,甚至可以 追踪用户如何移动鼠标(在页面使用不可见的字段),然而,使用目前我的方法只能追踪用户的第一次点击或悬停,我相信,修改我的方法最终可以实现追踪用户的每次点击
最后,我们还可以监测用户是否安装了某个特殊的字体,基于这个信息,我们可以追踪用户使用的 操作系统,因为不同操作系统使用的字体也稍有不同,例如 Windows 的 Calibri
这又是如何实现的
普通的做法
用 CSS 你可以使用 url(“foo.bar”) 属性引用外部资源添加图像,有趣的是,这个资源只在需要的时候被加载(例如,当链接被点击时)
所以,我们可以用 CSS 创建一个选择器,当用户点击某个链接时调用某个特定的 UPL
#link2:active::after { content:url('track.php?action=link2_clicked'); }
服务端,php 脚本会在调用 URL 时保存时间戳
浏览器监测
浏览器监 测是基于 @supports Media-Query 的,我们可以监测浏览器的一些特殊的属性,例如 -webkit-appearance
@supports(-webkit-appearance:none){ #chrome_detect::after { content:url('track.php?action=browser_chrome'); } }
字体监测
对于 字体监测,需要定义一个新的字体,如果一个字体存在,文本会尝试使用该字体进行样式设置,然而,当用户在系统上找不到该字体时,定义的字体会作为备用,在这种情况下,浏览器会尝试去加载定义的字体并在服务器上调用追踪脚本
/** Font detection **/ @font-face{ font-family:Font1; src:url('track.php?action=font1'); } #font_detection1 { font-family:Calibri,Font1; }
悬停监测
对于 悬停监测(基于 jeyroik 的想法),我们需定义一个关键帧,每次使用这个关键帧都要去请求一个 URL
@keyframespulsate { %{ background-image:url('track.php?duration=00'); } 20%{ background-image:url('track.php?duration=20'); } 40%{ background-image:url('track.php?duration=40'); } 60%{ background-image:url('track.php?duration=60'); } 80%{ background-image:url('track.php?duration=80'); } 100%{ background-image:url('track.php?duration=100'); } }
然后,我们使用定义的关键帧创建动画,我们可以定义动画持续的时间,这也是我们测量的最大时间
#duration:hover::after { -moz-animation:pulsate5sinfinite; -webkit-animation:pulsate5sinfinite; /*animation: pulsate 5s infinite;*/ animation-name:pulsate; animation-duration:10s; content:url('track.php?duration=-1'); }
我们可以通过补充关键帧的设置,来优化分辨率的监测
输入监测
监测用户选中了某个复选框,我们可以使用 CSS 提供的 :selected 选择器
#checkbox:checked { content:url('track.php?action=checkbox'); }
为了监测字符串,我们结合了 HTML pattern 属性,它可以帮助我们解决一些基本的输入验证,再结合 :valid 选择器,浏览器当输入匹配成功时会去请求我们的追踪站点
#text_input:valid { background:green; background-image:url('track.php?action=text_input'); }
如果属性后面没有任何 content 或有 php 警告 出现,这就意味着这个属性的值为 false 或用户还没访问页面或链接(这个,确实很烦,但你可以知道这些方法的原理)
此外,分辨率监测还不是特别的准确,因为目前只能监测最常用的屏幕宽度。最后还想说的是,监测用户实际屏幕的宽度并没有想象中的那么简单,因为 CSS 监测的高度为浏览器窗口的高度,而通常由于系统面板 / 任务栏的原因,使得浏览器窗口要小于显示器
有什么办法可以防止使用上面的方法进行追踪
目前我知道的唯一办法就是 完全禁用 CSS(你可以使用像 uMatrix 的插件来实现),但它的代价也是十分巨大的,没有 CSS,网页就没有之前那么赏心悦目了,甚至导致无法正常使用,所以,禁用 CSS 算不上一个真正的选择,除非,你实在担心你的隐私(例如,当你在使用 Tor 浏览器,也许你应该禁用 CSS)
一个更好的解决方案是,在网页加载时,浏览器不会去加载需要的外部资源,这样,就不可能监测到用户的个人行为,这种对内容加载的修改可以通过浏览器来实现,也可以通过插件来实现(类似 NoScript 或 uMatrix)
上述方法也存在一个明显的问题,那就是 对性能会造成一定的影响,因为浏览器会在初始化页面时加载大量的内容(有些内容是页面根本不需要的)
7、关于脚本压缩
推荐采用JSA压缩工具,原因:稳定、可靠、压缩质量有保证
不过需要大家安装一下Java RUNTIME
本文:如何用网页脚本追踪用户, JavaScript实现用户行为跟踪收集, js 分析客户行为, CSS 来追踪用户