实现本地换肤功能。具体描述:用户打开页面后可自由设定皮肤,但该皮肤配置不用存到服务端,用户用其他浏览器打开后显示默认皮肤即可。
把用户的设置存在cookie中(其他客户端本地存储方案也可以),用户打开界面时读取cookie中的配置,并设置相应皮肤。不同皮肤使用不同的class名,编写多套CSS样式代码,修改根元素的className即可设置皮肤。
对于cookie的读写要格外注意,如果代码书写不正确,容易出现Cookie读写错误的问题。
一个cookie包含如下组成部分:
Name
Value
Zero or more attributes
从上述结构中可以看到,一个cookie中必要的部分是name和value,即名字和取值;此外还可以有0个或多个其他属性。以上是cookie的形式化定义,具体设置cookie可以参考如下格式:
Set-Cookie:name=value[;expires=date][;domain=domain][;path=path][;secure][;httponly]
注意,我们可以认为cookie有六个域(其中name和value以name=value的形式放在第一个域中):name-value/expires/domain/path/secure/httponly
。其含义分别如下:
name-value: 要设置的cookie属性名称及其取值
expires: 当前cookie的过期时间
max-age: 最长存活时间(多数浏览器不支持)
domain: 当前cookie所属域
path: 当前cookie所属路径
secure: 安全性(是否加密传输等)
httponly: 可访问性(只能通过http或https访问)
其中name-value
是必须的,其他域是可选部分。除name和value外,浏览器不会发送其他属性到服务器去。这些属性是浏览器用来判定何时删除一个cookie,何时锁定一个cookie或者何时将一个cookie发送到服务端的。如果不注意上述四个可选部分,可能会对换肤效果的实现有重大影响。
更多内容请参考 Wikipedia HTTP cookie
Cookie可以有多种实现方式,在JavaScript中,可以使用document.cookie来访问当前document的cookie。
document.cookie = name + "=" + value;
注意,这里的=
不是赋值的意思,而是添加一个cookie到document.cookie中。一个页面中可以有多个cookie。或许,我们这么理解更合理,document.cookie是当前页面的所有cookie的集合(如果叫document.cookies会不会更合理呢),
=
可以认为是add或push。
在JavaScript中可以使用document.cookie获取当前页面的cookie。当cookie有多个时,以;
分割,形如:x=1;y=2
。如果要读取,需先用;
分割,然后在用=
分割,得到name和value。需要注意的是,name是有可能重复的,这还要结合domain和path来说,详见后续Domain和path对cookie的影响。
Cookie的覆盖
document.cookie = name + “=” + newvalue;
与cookie的设置一样,直接设置新值即可。
Cookie的删除
document.cookie = name + “=” + value + “;expires=” + earlyDateTime
Cookie并没有形如remove或delete的方法,其主动删除需要通过设置过期时间来删除,将过期时间设定为比当前时间较早的一个时间即可。下面是一个可能的删除方法,需要指定正确的domain/path/name才能正确的(如你所愿的)的删除某个cookie。
/**
* 删除domain/path下的某cookie
*
* @param {string} domain 域名
* @param {string} path 路径
* @param {string} name 要删除的cookie
*/
utils.deleteCookie = function (domain, path, name) {
name = escape(name);
var exp = new Date();
exp.setTime(exp.getTime() - 1000); // 过期时间为当前时间的前一秒
document.cookie = name + "=deleted;domain=" + domain +";path="+ path +";exp=" + exp;
}
Domain和Path用于定义一个cookie的作用范围,用于告知浏览器cookie属于哪个网址。安全起见,cookie只能被当前域名和子域名设置,其他域名及其子域名不可访问。
如果不指定cookie的domain和path,则其默认值就是该资源被请求的domain和path。然而,对于domain,默认的domain和显示设定的domain有一点不同:对于默认者,cookie只能被当前域名访问,对于手动显示设定者,当前域名及其子域名都可以访问。另外,对于path,根路径下的cookie,其子路径都可以访问;子路径下的cookie,根路径无法获得。
基于domain和path的作用,一个页面中的cookie是可能重名的,其domain和path必有一者与其他cookie不同。然而,我们在使用document.cookie获取cookie时只能读到name和value,无法读到domain和path,这也给我们读取cookie带来了一定的麻烦。在chrome控制台->Resources下可以看到各cookie,包括其所有属性。
在本例中,我们的原意就是在某域名下的任意域名设定cookie,然后令所有页面都可以共享这个cookie,因此,我们在设定cookie时手动显示设定path为跟路径path=/,域名不手动设置(其默认值都是当前域名),这样,在设定时都是操作的同一域名(domain)同一路径(path)下的同名cookie,读取时也仅有这一个同名cookie,即可达到要求。
如果不设置expires,在关闭浏览器时,cookie即被删除。因此为了保存skin,我们在设置cookie时把expires设为一年以后。
/**
* Add a cookie key-value pair.
* 在path=/下添加cookie
*
* @param {string} key 名
* @param {string||object||number|...} val 值
* @param {number} expireDays 过期天数
*/
utils.setCookie = function (key, val, expireDays) {
var dateTime = new Date();
dateTime.setTime(dateTime.getTime() + expireDays * 24 * 60 * 60 * 1000);
document.cookie = key + "=" + val + ";path=/;expires=" + dateTime;
};
Secure和httponly没有对应的value,只根据是否有该属性来决定其是否生效。Secure用于指定是否加密或通过安全连接来传输cookie。Httponly用于决定cookie的可访问性,httponly的cookie只能通过http或https来获取,其他方式(如JavaScript的document.cookie)不能获取。
在对cookie的设定和读取时,统一指定path=/
,保证各页面读取cookie时对同一cookie进行操作。
var utils = {};
/**
* 设置皮肤 Set the skin (By setting the className of the body element)
*/
utils.loadSkin = function () {
// get the body element, and it was used to be the skin wrapper
var skinWrapper = document.body;
// if the wrapper exist, attempt to set selected skin
if (skinWrapper) {
// 默认皮肤
skinWrapper.className = 'default-skin';
// 获得皮肤的cookie
var skinCookies = utils.getCookies(utils.COOKIE_KEY.SKIN);
if (skinCookies && skinCookies.length) {
// 如果有一个或多个,则使用第一个
skinWrapper.className = skinCookies[0];
}
}
};
/**
* Set the skin into cookie
* 可供界面元素调用,设定皮肤
*
* @param {string} skin, The skin class name
*/
utils.setSkin = function (skin) {
utils.setCookie(utils.COOKIE_KEY.SKIN, skin, 365);
utils.loadSkin();
};
/**
* Add a cookie key-value pair.
* 在path=/下添加cookie
*
* @param {string} key 名
* @param {string||object||number|...} val 值
* @param {number} expireDays 过期天数
*/
utils.setCookie = function (key, val, expireDays) {
key = escape(key);
val = escape(val);
var dateTime = new Date();
dateTime.setTime(dateTime.getTime() + expireDays * 24 * 60 * 60 * 1000);
document.cookie = key + "=" + val + ";path=/;expires=" + dateTime;
};
/**
* 拿到cookie中某个key对应的所有value
*
* @param {string} name cookie的key
* @returns {Array<string>} 一系列cookie
*/
utils.getCookies = function (name) {
name = escape(name);
var values = [];
if(!document.cookie == ''){
//用spilt('; ')切割所有cookie保存在数组arrCookie中
var arrCookie = document.cookie.split('; ');
var arrLength = arrCookie.length;
var keyValue;
for(var i=0; i<arrLength; ++i) {
keyValue = arrCookie[i].split('=');
if (keyValue[0] == name) {
values.push(unescape(keyValue[1]));
}
}
}
return values;
};
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Cookie换肤</title>
<link rel="stylesheet" href="style.css">
<script type="text/javascript" src="setting-skin.js"></script>
</head>
<body onload="utils.loadSkin()">
<input type="button" onclick="utils.setSkin('yellow')" value="set yellow skin">
<input type="button" onclick="utils.setSkin('red')" value="set red skin">
<input type="button" onclick="utils.setSkin('blue')" value="set blue skin">
<div class="content">
Content
</div>
</body>
</html>
.content {
margin-top: 20px;
padding: 40px;
background-color: #000;
color: #fff;
}
.red .content {
background-color: #f00;
}
.yellow .content {
background-color: #ff0;
}
.blue .content {
background-color: #00f;
}
完整代码请见github。
注意:代码须在服务器环境下方可生效,cookie的读写须由浏览器与服务器交互
示例中,点击不同按钮设置了不同的skin;点击刷新按钮是为了验证,skin的配置保存在了本地。