javascript实现一个简单的广告位, 发送数据请求使用 1×1 像素的透明 gif 图片, 网页中的广告统计,展现次数和点击次数

 

 

layout: post title : javascript实现一个简单的广告位 description : 一般广告位的实现都是一个独立的模块,哪里需要添加广告位,就把这个广告位插在哪里;而这个广告位通常都是由一个js链接导入的。 category : javascript

tags : [javascript, ad, advertisemen]

{% include JB/setup %}

一般广告位的实现都是一个独立的模块,哪里需要添加广告位,就把这个广告位插在哪里;而这个广告位通常都是由一个js链接导入的。

在这段广告位的js代码里,主要的功能点有:

  1. 代码采用闭包的方式,防止变量污染全局;
  2. 采用可配置项的方式进行调用:可以配置广告展示的开始时间、结束时间、广告位的宽和高;
    jumeiForU.init({
        "start":"2015/02/01 00:00",
        "end":"2016/01/01 00:00",
        "width":400,
        "height":400
    });

     

  1. 每次刷新页面均采用随机数的方式进行广告的展示;如果想要进行顺序循环展示的话,那就得读写cookie了;
  2. 读取引用该js链接中所带的参数,比如下面的广告链接,我们能够获取到referer参数的值。
<script type="text/javascript" src="http://xxx.js?referer=wenzi"></script>

以下放出代码,大家可进行参考,欢迎批评建议:

;(function(){
    var jumeiForU = {
        // 初始化
        init : function(_config){
            this.config = this.extend(this.config, _config);
            this.show();
        },
        // 广告展示及跳转链接
        data : [
            {'title':'九朵云祛斑霜', 'img':'http://p0.jmstatic.com/g/300x250/ht150122p854446t1.jpg', 'url':'http://www.jumeiglobal.com/deal/ht150122p854446t1.html?referer='},
            {'title':'Guerisson奇迹马油24K金面膜贴', 'img':'http://p0.jmstatic.com/g/300x250/ht150122p1293256t1.jpg', 'url':'http://www.jumeiglobal.com/deal/ht150122p1293256t1.html?referer='},
            {'title':'Its-skin晶钻蜗牛面膜', 'img':'http://p0.jmstatic.com/g/300x250/ht150122p818496t1.jpg', 'url':'http://www.jumeiglobal.com/deal/ht150122p818636t1.html?referer='},
            {'title':'九朵云美白祛斑气垫BB霜', 'img':'http://p0.jmstatic.com/g/300x250/ht150122p1293254t1.jpg', 'url':'http://www.jumeiglobal.com/deal/ht150122p1293254t1.html?referer='},
            {'title':'可莱丝NMF水库针剂睡眠面膜5片', 'img':'http://p0.jmstatic.com/g/300x250/ht150122p1312153t1.jpg', 'url':'http://www.jumeiglobal.com/deal/ht150122p1312153t1.html?referer='},
            {'title':'猪皮面膜', 'img':'http://p0.jmstatic.com/g/300x250/ht150122p1254465t1.jpg', 'url':'http://www.jumeiglobal.com/deal/ht150122p1254465t1.html?referer='},
            {'title':'奇迹马油精华套装', 'img':'http://p0.jmstatic.com/g/300x250/ht150122p1293257t1.jpg', 'url':'http://www.jumeiglobal.com/deal/ht150122p1293257t1.html?referer='},
            {'title':'九朵云美白祛斑套组', 'img':'http://p0.jmstatic.com/g/300x250/ht150122p1293255t1.jpg', 'url':'http://www.jumeiglobal.com/deal/ht150122p1293255t1.html?referer='},
            {'title':'晶钻蜗牛修护睡眠面膜', 'img':'http://p0.jmstatic.com/g/300x250/ht150122p818636t1.jpg', 'url':'http://www.jumeiglobal.com/deal/ht150122p818496t1.html?referer='},
            {'title':'skin1004僵尸面膜', 'img':'http://p0.jmstatic.com/g/300x250/ht150122p1265333t1.jpg', 'url':'http://www.jumeiglobal.com/deal/ht150122p1265333t1.html?referer='}
        ],
        // 配置
        config : {
            start : '2015/01/01 00:00',
            end : '2030/01/01 00:00',
            width : 300,
            height : 250
        },
        // 广告展示
        show : function(){
            var nowtime = (new Date()).getTime(),
                starttime = (new Date(this.config.start)).getTime(),
                endtime = (new Date(this.config.end)).getTime(),
                random = this.getRandom(),
                referer = this.getCurrentScript('referer');
            if(nowtime>=starttime && nowtime<endtime){
                document.write('<div style="position:relative; padding:0; margin:0; width:'+this.config.width+'px; height:'+this.config.height+'px"><a href="'+this.data[random].url+referer+'" target="_blank"><img src="'+this.data[random].img+'" alt="" style="width:'+this.config.width+'px; height:'+this.config.height+'px; border:0;" /></a></div>');
            }
        },
        // 返回当前需要展示的广告代号
        getRandom : function(){
            return Math.floor(Math.random()*this.data.length);
        },
        extend : function(destination, source) {
            for (var property in source) {
                destination[property] = source[property];
            }
            return destination;
        },
        // 获取referer的值
        getCurrentScript : function(name){
            var i = 0,
                result = null,
                script, scripts, url, reg, r;
            
            // firefox支持currentScript属性
            if( document.currentScript ){
                script = document.currentScript
            }
            else{
                // 正常情况下,在页面加载时,当前js文件的script标签始终是最后一个
                scripts = document.getElementsByTagName( 'script' )            
                script = scripts[ scripts.length - 1 ]
            }           
            url = script.hasAttribute ? script.src : script.getAttribute( 'src', 4 );
            reg = new RegExp("(^|&|\\?)" + name + "=([^&]*)(&|$)", "i");
            r = url.substr(1).match(reg);
            if (r !== null && r !==""){
                result = decodeURIComponent(r[2]);
            }
            return result===""? null: result;
        }
    }
    jumeiForU.init();
})(window);

 

为什么通常在发送数据埋点请求的时候使用的是 1×1 像素的透明 gif 图片?

 

作用:工作中,用于前端监控,比如曝光等等,谷歌和百度的都是用的1×1 像素的透明 gif 图片;
why?

  1. 没有跨域问题,一般这种上报数据,代码要写通用的;(排除ajax)
  2. 不会阻塞页面加载,影响用户的体验,只要new Image对象就好了;(排除JS/CSS文件资源方式上报)
  3. 在所有图片中,体积最小;(比较PNG/JPG)
  4. 能够完成整个 HTTP 请求+响应(尽管不需要响应内容)
  5. 触发 GET 请求之后不需要获取和处理数据、服务器也不需要发送数据
  6. 跨域友好
  7. 执行过程无阻塞
  8. 相比 XMLHttpRequest 对象发送 GET 请求,性能上更好
  9. GIF的最低合法体积最小(最小的BMP文件需要74个字节,PNG需要67个字节,而合法的GIF,只需要43个字节)

 

参考资料:
SegmentFault 上的回答
Web beacon
Using a Beacon Image for GitHub, Website and Email Analytics
为什么前端监控要用 GIF 打点

 

英文术语叫:image beacon
在Google 的 Make the Web Faster 的 #Track web traffic in the background 中有提到。

主要应用于只需要向服务器发送数据(日志数据)的场合,且无需服务器有消息体回应。比如收集访问者的统计信息。

一般做法是服务器用一个1×1的gif图片来作为响应,当然这有点浪费服务器资源。因此用header来响应比较合适,目前比较合适的做法是服务器发送”204 No Content”,即“服务器成功处理了请求,但不需要返回任何实体内容”。

另外该脚本的位置一般放在页面最后以免阻塞页面渲染,并且一般情况下也不需要append到DOM中。通过它的onerror和onload事件来检测发送状态。

 

google analysis的原理:
当我们访问带有Google Analyitcs追踪代码的页面时,页面中的GA追踪代码被执行,然后会向Google服务器发送一个1像素的图片请求。 (google-analytics.com/__?params1=XX&params2=XX)并将所收集到的数据(比如浏览器版本啊)作为请求__utm.gif图片链接的变量一起发送回google服务器。然后经过google服务器的处理发布到我们的数据报告里

 

浏览器什么时候会发起网络请求,去加载一张图片?

 

浏览器在什么时候会去加载一张图片呢?当然是我们网页中有图片的时候。在平时的项目开发中,我们还常常会用图片进行日志上报,大概像下面这样:

var img = new Image();
img.src = 'http://上报地址?a=1'

新建一个Image,将其src赋值,浏览器便会发出一个网络请求。

再来考虑一下下面的代码:

var scriptEl = document.createElement('script');
scriptEl.src = 'http://xxxx.js';

上面的代码会发起网络请求么?

答案是:不会。只需要增加一行代码,将script插入到DOM树中,便会触发网络请求。

var scriptEl = document.createElement('script');
scriptEl.src = 'http://xxxx.js';
document.body.appendChild(scriptEl); // 这行代码会触发请求

 

什么时候会触发一次图片请求?

再来看下面的代码:

var imgStr = '<img src="xxx.png">';
var divEl = document.createElement('div');
divEl.innerHTML = imgStr;

上面的代码会触发网络请求么?答案是:会的。虽然divEl并没有被插入到DOM树中,但是网络请求依然会触发。这种表现一开始是让我有点意外的。于是乎,我开始了探索。

翻了一下html规范,发现了下面这段话:

地址:html.spec.whatwg.org/multipage/i…

大概的意思就是:

如果浏览器禁用了js脚本,那么浏览器可以立即请求图片,也可以根据需要加载图片。如果浏览器没有禁用js脚本,当img元素被创建,或者经历一些变化(src被赋值等) 时,浏览器必须立即进入一个update the image data的流程。在update the image data这个流程中,如果图片元素还没有内容,并且图片元素的src已被赋值,浏览器会立即发起请求去请求图片。

这样,就可以解释上述的现象了:

 

Case 1

var img = new Image();
img.src = 'http://上报地址?a=1'

// Object.prototype.toString.call(img) === "[object HTMLImageElement]"

使用Image构造函数时,生成了一个HTMLImageElement实例,也就是一个img元素,然后给这个img元素的src赋了值。很显然,我们使用了javascript。按照规范,浏览器必须立即发起网路请求,更新图片数据。

 

Case 2

var imgStr = '<img src="xxx.png">';
var divEl = document.createElement('div');
divEl.innerHTML = imgStr;

// Object.prototype.toString.call(divEl.firstChild) === "[object HTMLImageElement]"

在使用了innerHTML后,其实我们也是生成了一个HTMLImageElement实例。按照规范,浏览器也必须立即发起网络请求,更新图片数据。

总结一下:只要我们在代码中创建了一个img元素(HTMLImageElement实例),并且我们给这个img元素的src赋值了,那么浏览器就会发起网络请求,加载图片内容

 

什么时候触发一次script请求?

我们再来规范里是怎么规定script标签的。

地址:html.spec.whatwg.org/multipage/s…

在script标签的相关规范里,我没有找到明确的说明在什么情况下需要发起网络请求加载资源。另外,我看到了上面红框里的这段话。概括一下,就是:

浏览器可以在设置script元素的src时候,就发起网络请求加载资源。但是如果最后,这个script元素没有插入DOM的话,网络请求就完全被浪费了。

可以理解为:浏览器可以在设置script元素的时候,自行考虑是否需要立即发起网络请求加载资源。然后浏览器在实现的时候,为了节约资源,并没有立即发起请求,而是选择了在插入DOM树后,才发起请求。

(这里不是很确定,但是没有找到更进一步的说明)

 

跳出规范来思考

不考虑规范,在平时的业务中,我们新建图片后,不管图片是否最终插入DOM树,都需要立即拿到图片的信息,比如canvas,比如通过图片的宽高进行页面排版等。因此,新建img元素后立即发起请求拿到图片数据,是合乎逻辑的。

对于script元素来说,也不存在需要单独操作的场景,为了节省资源,script插入DOM树后再发起网络请求,也是合乎逻辑的。

 

 

本文:javascript实现一个简单的广告位, 发送数据请求使用 1×1 像素的透明 gif 图片, 网页中的广告统计,展现次数和点击次数

Leave a Reply