需求描述

:smiley:

实现本地换肤功能。具体描述:用户打开页面后可自由设定皮肤,但该皮肤配置不用存到服务端,用户用其他浏览器打开后显示默认皮肤即可。

实现方案

把用户的设置存在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。其含义分别如下:

  1. name-value: 要设置的cookie属性名称及其取值
  2. expires: 当前cookie的过期时间
  3. max-age: 最长存活时间(多数浏览器不支持)
  4. domain: 当前cookie所属域
  5. path: 当前cookie所属路径
  6. secure: 安全性(是否加密传输等)
  7. httponly: 可访问性(只能通过httphttps访问)

其中name-value是必须的,其他域是可选部分。除name和value外,浏览器不会发送其他属性到服务器去。这些属性是浏览器用来判定何时删除一个cookie,何时锁定一个cookie或者何时将一个cookie发送到服务端的。如果不注意上述四个可选部分,可能会对换肤效果的实现有重大影响。

更多内容请参考 Wikipedia HTTP cookie

Cookie可以有多种实现方式,在JavaScript中,可以使用document.cookie来访问当前document的cookie。

  1. document.cookie = name + "=" + value;

注意,这里的=不是赋值的意思,而是添加一个cookie到document.cookie中。一个页面中可以有多个cookie。或许,我们这么理解更合理,document.cookie是当前页面的所有cookie的集合(如果叫document.cookies会不会更合理呢:joy:),=可以认为是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。

  1. /**
  2. * 删除domain/path下的某cookie
  3. *
  4. * @param {string} domain 域名
  5. * @param {string} path 路径
  6. * @param {string} name 要删除的cookie
  7. */
  8. utils.deleteCookie = function (domain, path, name) {
  9. name = escape(name);
  10. var exp = new Date();
  11. exp.setTime(exp.getTime() - 1000); // 过期时间为当前时间的前一秒
  12. document.cookie = name + "=deleted;domain=" + domain +";path="+ path +";exp=" + exp;
  13. }

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

如果不设置expires,在关闭浏览器时,cookie即被删除。因此为了保存skin,我们在设置cookie时把expires设为一年以后。

  1. /**
  2. * Add a cookie key-value pair.
  3. * 在path=/下添加cookie
  4. *
  5. * @param {string} key 名
  6. * @param {string||object||number|...} val 值
  7. * @param {number} expireDays 过期天数
  8. */
  9. utils.setCookie = function (key, val, expireDays) {
  10. var dateTime = new Date();
  11. dateTime.setTime(dateTime.getTime() + expireDays * 24 * 60 * 60 * 1000);
  12. document.cookie = key + "=" + val + ";path=/;expires=" + dateTime;
  13. };

Secure和httponly没有对应的value,只根据是否有该属性来决定其是否生效。Secure用于指定是否加密或通过安全连接来传输cookie。Httponly用于决定cookie的可访问性,httponly的cookie只能通过http或https来获取,其他方式(如JavaScript的document.cookie)不能获取。

总结

在对cookie的设定和读取时,统一指定path=/,保证各页面读取cookie时对同一cookie进行操作。

关键代码

JavaScript

  1. var utils = {};
  2. /**
  3. * 设置皮肤 Set the skin (By setting the className of the body element)
  4. */
  5. utils.loadSkin = function () {
  6. // get the body element, and it was used to be the skin wrapper
  7. var skinWrapper = document.body;
  8. // if the wrapper exist, attempt to set selected skin
  9. if (skinWrapper) {
  10. // 默认皮肤
  11. skinWrapper.className = 'default-skin';
  12. // 获得皮肤的cookie
  13. var skinCookies = utils.getCookies(utils.COOKIE_KEY.SKIN);
  14. if (skinCookies && skinCookies.length) {
  15. // 如果有一个或多个,则使用第一个
  16. skinWrapper.className = skinCookies[0];
  17. }
  18. }
  19. };
  20. /**
  21. * Set the skin into cookie
  22. * 可供界面元素调用,设定皮肤
  23. *
  24. * @param {string} skin, The skin class name
  25. */
  26. utils.setSkin = function (skin) {
  27. utils.setCookie(utils.COOKIE_KEY.SKIN, skin, 365);
  28. utils.loadSkin();
  29. };
  30. /**
  31. * Add a cookie key-value pair.
  32. * 在path=/下添加cookie
  33. *
  34. * @param {string} key 名
  35. * @param {string||object||number|...} val 值
  36. * @param {number} expireDays 过期天数
  37. */
  38. utils.setCookie = function (key, val, expireDays) {
  39. key = escape(key);
  40. val = escape(val);
  41. var dateTime = new Date();
  42. dateTime.setTime(dateTime.getTime() + expireDays * 24 * 60 * 60 * 1000);
  43. document.cookie = key + "=" + val + ";path=/;expires=" + dateTime;
  44. };
  45. /**
  46. * 拿到cookie中某个key对应的所有value
  47. *
  48. * @param {string} name cookie的key
  49. * @returns {Array<string>} 一系列cookie
  50. */
  51. utils.getCookies = function (name) {
  52. name = escape(name);
  53. var values = [];
  54. if(!document.cookie == ''){
  55. //用spilt('; ')切割所有cookie保存在数组arrCookie中
  56. var arrCookie = document.cookie.split('; ');
  57. var arrLength = arrCookie.length;
  58. var keyValue;
  59. for(var i=0; i<arrLength; ++i) {
  60. keyValue = arrCookie[i].split('=');
  61. if (keyValue[0] == name) {
  62. values.push(unescape(keyValue[1]));
  63. }
  64. }
  65. }
  66. return values;
  67. };

HTML

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title>Cookie换肤</title>
  6. <link rel="stylesheet" href="style.css">
  7. <script type="text/javascript" src="setting-skin.js"></script>
  8. </head>
  9. <body onload="utils.loadSkin()">
  10. <input type="button" onclick="utils.setSkin('yellow')" value="set yellow skin">
  11. <input type="button" onclick="utils.setSkin('red')" value="set red skin">
  12. <input type="button" onclick="utils.setSkin('blue')" value="set blue skin">
  13. <div class="content">
  14. Content
  15. </div>
  16. </body>
  17. </html>

CSS

  1. .content {
  2. margin-top: 20px;
  3. padding: 40px;
  4. background-color: #000;
  5. color: #fff;
  6. }
  7. .red .content {
  8. background-color: #f00;
  9. }
  10. .yellow .content {
  11. background-color: #ff0;
  12. }
  13. .blue .content {
  14. background-color: #00f;
  15. }

完整代码请见github

效果

注意:代码须在服务器环境下方可生效,cookie的读写须由浏览器与服务器交互

效果图

151226000812.gif
示例中,点击不同按钮设置了不同的skin;点击刷新按钮是为了验证,skin的配置保存在了本地。