
从事软件研发十余年,入行前端开发六年有余,对前端工程化有一定认识。 现就职于客如云科技有限公司,任前端技术经理一职。
前端工程化概述 什么是工程化 定义 工程化即系统化、模块化、规范化的一个过程。 如果说计算机科学要解决的是系统的某个具体问题,或者更通俗点说是面向编码的,那么工程化要解决的是如何提高整个系统生产效率。 与其说软件工程是一门科学,不如说它更偏向于管理学和方法论。 解决什么问题 工程化解决的问题是,如何提高编码、测试、维护阶段的生产效率。 什么是前端 维基百科 前端Front-end和后端back-end是描述进程开始和结束的通用词汇。 前端作用于采集输入信息,后端进行处理。计算机程序的界面样式,视觉呈现属于前端。 百度百科 前端对于网站来说,通常是指,网站的前台部分包括网站的表现层和结构层。 因此前端技术一般分为前端设计和前端开发,前端设计一般可以理解为网站的视觉设计,前端开发则是网站的前台代码实现,包括基本的HTML和CSS以及JavaScript/ajax,现在最新的高级版本HTML5、CSS3,以及SVG等。 __ 前端发展史 1. 简单明快的早期时代 适合小项目,不分前后端,页面由JSP、PHP等在服务端生成,浏览器负责展现。 2. 后端为主的 MVC 时代 为了降低复杂度,以后端为出发点,有了Web Server层的架构升级,比如Structs、Spring MVC等。 前两个阶段存在的问题 前端开发重度依赖开发环境。 前后端职责依旧纠缠不清,可维护性越来越差。 3. Ajax 带来的 SPA 时代 2005年Ajax正式提出,前端开发进入SPA(Single Page Application 单页面应用)时代。 Ajax时代面临的挑战 前后端接口的约定。 前端开发的复杂度控制。SPA应用大多以功能交互型为主,大量 JS 代码的组织,与 View 层的绑定等,都不是容易的事情。 4. 前端为主的 MVC、MV* 时代 为了降低前端开发复杂度,Backbone、EmberJS、KnockoutJS、AngularJS、React、Vue等大量前端框架涌现。 5. Node 带来的全栈时代 随着Node.js的兴起,为前端开发带来一种新的开发模式。 业界比较出名的实践是,阿里巴巴的中途岛计划。 后两个步骤带来的好处 前后端职责很清晰。 前端开发的复杂度可控,通过合理的分层,让项目更可维护。 部署相对独立,产品体验可以快速改进。 前端工程化要解决什么 1. 制定各项规范,让工作有章可循 统一团队成员的编码规范,便于团队协作和代码维护 目录结构,文件命名规范 编码规范:eslint, stylelint 开发流程的规范 使用敏捷,增强开发进度管理和控制 应对各项风险,需求变更等 code review 机制 UAT 提升发布的需求的质量 前后端接口规范,其他文档规范 2. 使用版本控制工具,高效安全的管理源代码 使用 git 版本控制工具 Git分支管理 Commit描述规范,例如:task-number + task 描述 创建 merge request,code review 完毕之后方可合并代码 3. 使用合适的前端技术和框架,提高生产效率,降低维护难度 目标: 职责分离、降低耦合,增强代码的可读性、维护性和测试性。 采用模块化的方式组织代码 JS 模块化:AMD、CommonJS、UMD、ES6 Module CSS 模块化:less、sass、stylus、postCSS、css module 采用组件化的编程思想,处理 UI 层 react、vue、angular 将数据层分离管理 redux、mbox 使用面向对象或者函数编程的方式组织架构 4. 提高代码的可测试性,引入单元测试,提高代码质量 5. 通过使用各种自动化的工程工具,提升整个开发、部署效率 JavaScript 对模块化的现实需要 js设计之初,主要用于处理简单的页面效果和逻辑验证之类的简单工作。 被过于随意的设计,缺乏模块化一直是其缺陷之一。 随着单页应用与富客户端的流行,不断增长的代码库也急需合理的代码分割与依赖管理的解决方案,这就是我们在软件工程领域所熟悉的模块化。 模块化主要解决的问题:解决代码分割、增强维护性、提高代码重用、作用域隔离、模块之间的依赖管理、发布的自动化打包与处理等多个方面。 主要的模块化方案 直接声明依赖(Directly Defined Dependences) 命名空间(Namespace Pattern) 模块模式(Module Pattern) 依赖分离定义(Detached Dependency Definitions) 沙盒(Sandbox) 依赖注入(Dependency Injection) 标签化模块(Labeled Modules) CommonJS、AMD、UMD、ES 2015 Modules 1. 直接声明依赖 除了备注描述,什么问题都没解决 // file app.js var helloInLang = { en: 'Hello world!' }; // file hello.js /* helloInLang defined in app.js */ function writeHello(lang) { document.write(helloInLang[lang]); }; 2. 命名空间 命名空间模式始于2002年,使用特殊的约定命名,用于避免命名冲突和全局作用域污染。 缺点:大型项目可维护性较差,没有解决模块间依赖管理的问题。 // file app.js var app = {}; // file greeting.js app.helloInLang = { en: 'Hello world!' }; // file hello.js app.writeHello = function (lang) { document.write(app.helloInLang[lang]); }; 3. 模块模型 封装了变量和function,和全局的namaspace不接触,松耦合 只暴露可用public的方法,其它私有方法全部隐藏 // file app.js var app = {}; // file greeting.js app = (function (app) { var _app = app || {}; _app.helloInLang = { en: 'Hello world!' }; return _app; } (app)); // file hello.js app = (function (app) { var _app = app || {}; _app.writeHello = function (lang) { document.write(app.helloInLang[lang]); }; return _app; } (app)); 4. 依赖注入 2009年 Angular 引入 Java 世界的依赖注入思想。 核心思想:某个模块不需要手动初始化某个依赖对象,只需要声明该依赖,并由外部框架自动实例化该对象,并传递到模块内。 // file greeting.js angular.module('greeter', []) .value('greeting', { helloInLang: { en: 'Hello world!' }, ayHello: function(lang) { return this.helloInLang[lang]; } }); // file app.js angular.module('app', ['greeter']) .controller('GreetingController', ['$scope', 'greeting', function($scope, greeting) { $scope.phrase = greeting.sayHello('en'); }]); 5. CommonJS 服务器端 javascript 模块化解决方案,适用于同步模块加载。 // file greeting.js var helloInLang = { en: 'Hello world!' }; var sayHello = function (lang) { return helloInLang[lang]; } module.exports.sayHello = sayHello; // file hello.js var sayHello = require('./lib/greeting').sayHello; var phrase = sayHello('en'); console.log(phrase); 6. AMD 浏览器端 javascript 模块化解决方案,适用于异步模块加载。 // file lib/greeting.js define(function() { var helloInLang = { en: 'Hello world!' }; return { sayHello: function (lang) { return helloInLang[lang]; } }; }); // file hello.js define(['./lib/greeting'], function(greeting) { var phrase = greeting.sayHello('en'); document.write(phrase); }); 7. UMD UMD 允许在环境中同时使用 AMD 与 CommonJS 规范。 (function(define) { define(function () { var helloInLang = { en: 'Hello world!' }; return { sayHello: function (lang) { return helloInLang[lang]; } }; }); }( typeof module === 'object' && module.exports && typeof define !== 'function' ? function (factory) { module.exports = factory(); } : define )); 8. ES2015 Modules ES2015 Modules 作为 JavaScript 官方标准,日渐成为了开发者的主流选择。 // file lib/greeting.js const helloInLang = { en: 'Hello world!' }; export const greeting = { sayHello: function (lang) { return helloInLang[lang]; } }; // file hello.js import { greeting } from "./lib/greeting"; const phrase = greeting.sayHello("en"); document.write(phrase); 增强可测试性 采用tdd的编程思想,引入单元测试 karma + mocha + chai、jest、ava 使用各种调试工具 web devtools、finddle 自动化工程工具的使用 使用前端构建工具 gulp、grunt、Broccoli javascript 编译工具 Babel、Browserify、Webpack 开发辅助工具 数据 mock、livereload 使用CI集成工具 jenkins、Travis CI 使用脚手架工具 yeoman、create-app 客如云前端工程化实践 统一公司前端技术栈,根据职责创建不同项目。 kryfe-boilerplate 脚手架项目 kryfe-tools 通用工具库 kryfe-lib 通用类库 kryfe-component 公共组件库 eslint-config-kryfe eslint规范 stylelint-config-kryfe stylelint规范 kryfe-docs 各种规范文档 kryfe-style PC端样式库 参考资料 浅析前端工程化 前端工程——基础篇 前端开发工程化探讨--基础篇 前端文摘:Web 开发模式演变历史和趋势 JavaScript 模块演化简史
在今天的课程中,我将向大家介绍如何使用css3创建3d的立方体。大家可以通过下面的链接浏览实际效果,操作上下左右键,实现立方体的翻转效果。 demo地址:http://www.paulrhayes.com/experiments/cube-3d/ demo下载地址:animated-css3-cube.zip 下面我们开始介绍如何制作。 html: <div id="experiment"> <div id="cube"> <div class="face one"> One face </div> <div class="face two"> Up, down, left, right </div> <div class="face three"> Lorem ipsum. </div> <div class="face four"> New forms of navigation are fun. </div> <div class="face five"> Rotating 3D cube </div> <div class="face six"> More content </div> </div> </div> 上面的html中,class为face的6个div分别代表立方体的6个面,使用one到six的class名字对他们加以区分。外部包含有id为cube和experiment的两层容器,这两层都是必须的,少了任何一个,3d效果都出不来。 其中experiment起到镜头的作用。对他设置焦距,这样3d效果才能在内部元素上展示出来。 perspective属性定义z轴平面的深度,同时也定义了平面上面和下面元素的相对尺寸。总的来说,perspective值越小,物体越大,离你也越近;perspective值越大,物体越小,离你也越远。大家可以通过http://www.webkit.org/blog-files/3d-transforms/perspective-by-example.html查看效果。 perspective-origin属性定义视角的原点。 css: #experiment { -webkit-perspective: 800; -moz-perspective: 800; -o-perspective: 800; perspective: 800; -webkit-perspective-origin: 50% 200px; -moz-perspective-origin: 50% 200px; -o-perspective-origin: 50% 200px; perspective-origin: 50% 200px; } cube设置的属性中包含固定的宽度和高度,并将position设置为relative。固定的高度和宽度是必须的,这样cube中的元素才能在限定的区域内执行3d动画。如果将高度和宽度设置为100%,cube中的元素将在整个页面范围内运动。 transition用来设置动画相关的属性。transform-style: preserve-3d; 使得cube中的所有元素作为一个整体来产生3d效果。 大家可以浏览http://www.zhangxinxu.com/wordpress/2012/09/css3-3d-transform-perspective-animate-transition/,了解更多信息。 css: #cube { position: relative; margin: 100px auto; height: 400px; width: 400px; -webkit-transition: -webkit-transform 2s linear; -moz-transition: -moz-transform 2s linear; -o-transition: -o-transform 2s linear; transition: transform 2s linear; -webkit-transform-style: preserve-3d; -moz-transform-style: preserve-3d; -o-transform-style: preserve-3d; transform-style: preserve-3d; } 接下来统一为立方体的6个面设置css属性。 css: .face { position: absolute; height: 360px; width: 360px; padding: 20px; background-color: rgba(50, 50, 50, 0.7); } 接下来但是设置6个面的3d相关属性,使用rotateX或者rotateY来实现立方体表面朝向的翻转,使用translateZ实现立方体表面在3维空间的位置移动。 css: #cube .one { -webkit-transform: rotateX(90deg) translateZ(200px); -moz-transform: rotateX(90deg) translateZ(200px); -o-transform: rotateX(90deg) translateZ(200px); transform: rotateX(90deg) translateZ(200px); } #cube .two { -webkit-transform: translateZ(200px); -moz-transform: translateZ(200px); -o-transform: translateZ(200px); transform: translateZ(200px); } #cube .three { -webkit-transform: rotateY(90deg) translateZ(200px); -moz-transform: rotateY(90deg) translateZ(200px); -o-transform: rotateY(90deg) translateZ(200px); transform: rotateY(90deg) translateZ(200px); } #cube .four { -webkit-transform: rotateY(180deg) translateZ(200px); -moz-transform: rotateY(180deg) translateZ(200px); -o-transform: rotateY(180deg) translateZ(200px); transform: rotateY(180deg) translateZ(200px); } #cube .five { -webkit-transform: rotateY(-90deg) translateZ(200px); -moz-transform: rotateY(-90deg) translateZ(200px); -o-transform: rotateY(-90deg) translateZ(200px); transform: rotateY(-90deg) translateZ(200px); } #cube .six { -webkit-transform: rotateX(-90deg) translateZ(200px); -moz-transform: rotateX(-90deg) translateZ(200px); -o-transform: rotateX(-90deg) translateZ(200px); transform: rotateX(-90deg) translateZ(200px); } 最后要做的是为页面绑定keydown事件,这样当你按下上下左右键的时候,实现立方体的翻转运动效果。 javascript: var xAngle = 0, yAngle = 0; document.addEventListener('keydown', function(e) { switch(e.keyCode) { case 37: // left yAngle -= 90; break; case 38: // up xAngle += 90; break; case 39: // right yAngle += 90; break; case 40: // down xAngle -= 90; break; }; $('cube').style.webkitTransform = "rotateX("+xAngle+"deg) rotateY("+yAngle+"deg)"; }, false); 课程就讲到这里,接下来大家可以动手尝试一下。 原文地址:http://www.paulrhayes.com/2009-07/animated-css3-cube-interface-using-3d-transforms/
今天读了篇关于如何使用css3创建3d四面体的文章,觉的相当不错,所以拿出来和大家分享一下。原文地址:http://www.paulrhayes.com/2010-10/css-tetrahedron/。 demo预览地址:http://www.paulrhayes.com/experiments/pyramid/。 首先要和大家分享的是,如何使用div+css创建三角形。在这里我先把相关代码粘贴出来,然后再为大家讲解原理。 html: <div id="pyramid"> <div></div> </div> css: <style type="text/css"> #pyramid { position: relative; margin: 100px auto; height: 500px; width: 100px; } #pyramid > div { position: absolute; border-style: solid; border-width: 200px 0 200px 346px; } #pyramid > div:after { position: absolute; content: "Triangle"; color: #fff; left: -250px; text-align: center; } #pyramid > div:first-child { border-color: #ff0000 transparent #ff0000 rgba(50, 50, 50, 0.6); } </style> 运行效果: 原理解析: html代码中我们定义了两个div,外部div是容器对象,内部div用来生成三角形。css代码中,我们没有为内部div设置宽度和高度,只设置了border三个边的宽度(上、下和左)。通过为三个边设置不同颜色,他们会分别变成三个不同的三角形。 这时,我们只需要简单的将上下两边的颜色设置为透明色,一个等边三角形就出现了。 #pyramid > div:first-child { border-color: transparent transparent transparent rgba(50, 50, 50, 0.6); } 效果图: 其中,红圈所示的地方就是内部div所在位置。他是个看不见的,0宽度0高度,但又实际存在的对象。 我们接下来要讲的是如何实现3d四面体和如何创建动画。 首先还是粘贴相关的代码。 html: <div id="pyramid"> <div></div> <div></div> <div></div> <div></div> </div> css: <style type="text/css"> #pyramid { position: relative; margin: 100px auto; height: 500px; width: 100px; -webkit-transform-style: preserve-3d; -webkit-animation: spin 10s linear infinite; -webkit-transform-origin: 116px 200px 116px; -moz-transform-style: preserve-3d; -moz-animation: spin 10s linear infinite; -moz-transform-origin: 116px 200px 116px; -ms-transform-style: preserve-3d; -ms-animation: spin 10s linear infinite; -ms-transform-origin: 116px 200px 116px; transform-style: preserve-3d; animation: spin 10s linear infinite; transform-origin: 116px 200px 116px; } @-webkit-keyframes spin { from { -webkit-transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg); } to { -webkit-transform: rotateX(360deg) rotateY(360deg) rotateZ(360deg); } } @-moz-keyframes spin { from { -moz-transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg); } to { -moz-transform: rotateX(360deg) rotateY(360deg) rotateZ(360deg); } } @-ms-keyframes spin { from { -ms-transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg); } to { -ms-transform: rotateX(360deg) rotateY(360deg) rotateZ(360deg); } } @keyframes spin { from { transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg); } to { transform: rotateX(360deg) rotateY(360deg) rotateZ(360deg); } } #pyramid > div { position: absolute; border-style: solid; border-width: 200px 0 200px 346px; -webkit-transform-origin: 0 0; -moz-transform-origin: 0 0; -ms-transform-origin: 0 0; transform-origin: 0 0; } #pyramid > div:after { position: absolute; content: "Triangle"; color: #fff; left: -250px; text-align: center; } #pyramid > div:first-child { border-color: transparent transparent transparent rgba(50, 50, 50, 0.6); -webkit-transform: rotateY(-19.5deg) rotateX(180deg) translateY(-400px); -moz-transform: rotateY(-19.5deg) rotateX(180deg) translateY(-400px); -ms-transform: rotateY(-19.5deg) rotateX(180deg) translateY(-400px); transform: rotateY(-19.5deg) rotateX(180deg) translateY(-400px); } #pyramid > div:nth-child(2) { border-color: transparent transparent transparent rgba(50, 50, 50, 0.6); -webkit-transform: rotateY(90deg) rotateZ(60deg) rotateX(180deg) translateY(-400px); -moz-transform: rotateY(90deg) rotateZ(60deg) rotateX(180deg) translateY(-400px); -ms-transform: rotateY(90deg) rotateZ(60deg) rotateX(180deg) translateY(-400px); transform: rotateY(90deg) rotateZ(60deg) rotateX(180deg) translateY(-400px); } #pyramid > div:nth-child(3) { border-color: transparent transparent transparent rgba(50, 50, 50, 0.9); -webkit-transform: rotateX(60deg) rotateY(19.5deg); -moz-transform: rotateX(60deg) rotateY(19.5deg); -ms-transform: rotateX(60deg) rotateY(19.5deg); transform: rotateX(60deg) rotateY(19.5deg); } #pyramid > div:nth-child(4) { border-color: transparent transparent transparent rgba(50, 50, 50, 0.8); -webkit-transform: rotateX(-60deg) rotateY(19.5deg) translateX(-116px) translateY(-200px) translateZ(326px); -moz-transform: rotateX(-60deg) rotateY(19.5deg) translateX(-116px) translateY(-200px) translateZ(326px); -ms-transform: rotateX(-60deg) rotateY(19.5deg) translateX(-116px) translateY(-200px) translateZ(326px); transform: rotateX(-60deg) rotateY(19.5deg) translateX(-116px) translateY(-200px) translateZ(326px); } </style> 现在开始相关代码的讲解。 html代码和之前的差不多,就是多了三个div,分别作为四面体的另外三个面。 css代码中,我们使用 #pyramid > div:nth-child(n) 寻找到三面体的四个面,设置border四个边的颜色,将他们分别定义成三角形。通过transform属性的rotateX,rotateY,translateX,translateY和translateZ方法,设置他们在3维空间中的角度、朝向和位置。这里涉及到很多数学知识,大家需要去补充相关知识。 通过上述设置,四面体就形成了。接下来就是为其添加动画效果。这里使用的东西也很简单,就是animation和keyframes。css3相关属性,大家可以到http://www.w3schools.com/css3/default.asp站点去学习,我这里就不做过多讲解了。 本文到此为止,大家可以把html和css代码粘贴在一起,查看最终效果。 代码里面有不懂的内容,大家可以给我留言。
今天我们将介绍,如何使用css3完成google涂鸦动画。当你点击demo页面的【开始】按钮之后,页面中的骑手和马匹将会运动起来,http://www.mycookingroom.com/demo/google-doodle-animation-in-css3-without-javascript.html。 这里需要强调的一点是,ie不支持css3的动画属性,再次抱怨下万恶的ie。但是我们不能以此为理由不去拥抱css3。 我们先来看html代码。 <!DOCTYPE html> <html> <head> <title></title> <link rel="stylesheet" type="text/css" href="css/google-doodle-animation-in-css3-without-javascript.css"/> </head> <body> <div id="logo"> <div class="frame"> <img src="img/muybridge12-hp-v.png"/> </div> <label for="play_button" id="play_label"></label> <input type="checkbox" id="play_button" name="play_button"/> <span id="play_image"> <img src="img/muybridge12-hp-p.jpg"/> </span> <div class="horse"></div> <div class="horse"></div> <div class="horse"></div> </div> </body> </html> 下面是部分css。 *{margin:0px;padding:0px;} #logo{position: relative;} .horse{ width:469px; height:54px; background: url('../img/muybridge12-hp-f.jpg'); } .frame{position:absolute;left:0;top:0;z-index: 1;} #play_button{display: none;} #play_label{ width:67px; height:54px; display:block; position: absolute; left:201px; top:54px; z-index: 2; } #play_image{ position: absolute; left:201px; top:54px; z-index: 0; overflow: hidden; width: 68px; height: 55px; } #play_image img{ position: absolute; left: 0; top: 0; } 这部分代码没太大难度,我就不做详细讲解了。css基础不是很扎实的读者,也许会疑惑【开始】按钮是如何实现定位的。可以自行阅读position属性,了解absolute具体作用。 下面是上述html和css代码完成的页面效果。 下面我们来介绍如何产生动画效果。我们首先需要定义关键帧,他规定动画在不同阶段的效果。大家可以通过http://www.w3schools.com/css3/css3_animations.asp 了解更多信息。 我们创建了一个名为horse-ride的关键帧,针对chrome和firefox需要在前面添加-webkit-或者是-moz-前缀。0%和100%分别代码开始和结束,可以根据需要增加新的case,比如50%时的动画效果。 @-webkit-keyframes horse-ride { 0% {background-position: 0 0;} 100% {background-position: -804px 0;} } @-moz-keyframes horse-ride { 0% {background-position: 0 0;} 100% {background-position: -804px 0;} } 下面,我们来为horse添加css3的动画效果。 #play_button:checked ~.horse{ -webkit-animation:horse-ride 0.5s steps(12,end) infinite; -webkit-animation-delay:2.5s; -moz-animation:horse-ride 0.5s steps(12,end) infinite; -moz-animation-delay:2.5s; background-position: -2412px 0; -webkit-transition: all 2.5s cubic-bezier(0.550, 0.055, 0.675, 0.190); -moz-transition: all 2.5s cubic-bezier(0.550, 0.055, 0.675, 0.190); } 这里首先介绍:checked和~,:checked是伪类,指当#play_button选中时的css效果,~指的是#play_button的兄弟节点。 接下来介绍.horse相关的css属性。animation中我们使用了4个值,依次代表:关键帧(我们上面定义的horse-ride),动画间隔时间,动画效果和执行次数。之后我们又通过animation-delay设置动画延迟时间。通过transition和background-position集合起来,设置背景的过渡动画。 最后我们为【开始】按钮添加动画效果。 #play_button:checked ~#play_image img{ left:-68px; -webkit-transition: all 0.5s ease-in; -moz-transition: all 0.5s ease-in; } 大家可以自己动手尝试开发了。 demo下载地址:google-doodle-animation-in-css3-without-javascript.zip 原文地址:http://cssdeck.com/labs/google-doodle-for-eadweard-j-muybridges-182nd-birthday
介绍 低层次的语言,如C,具有低级别的内存管理命令,如:malloc()和free(),需要开发者手工释放内存。然而像javascript这样的高级语言情况则不同,对象(objects, strings 等)创建的时候分配内存,当他们不在使用的时候内存会被自动回收,这个自动回收的过程被称为垃圾回收。因为垃圾回收的存在,让javascript等高级语言开发者产生了一个错误的认识,以为可以不用关心内存管理。 内存生命周期 不管什么样的编程语言,内存的生命周期基本上是一致的。 分配你需要的内存 使用他进行读写操作 当内存不需要的时候,释放资源 步骤1和步骤2对于所有语言都一样,能明显觉察到。至于步骤3,低级别语言需要开发者显式执行。而对于像javascript这样的高级语言,这部分操作是交给解析器完成的,所以你不会觉察到。 javascript中的分配操作 值的初始化 在为变量赋值的时候,javascript会完成内存的分配工作。 var n = 123; // 为数字分配内存 var s = "azerty"; // 为字符串分配内存 var o = { a: 1, b: null }; // 为包含属性值的object对象分配内存 var a = [1, null, "abra"]; // 为包含值的数组分配内存 function f(a){ return a + 2; } // 为函数分配内存(函数是可调用的对象) // 函数表达式同样也是对象,存在分配内存的情况 someElement.addEventListener('click', function(){ someElement.style.backgroundColor = 'blue'; }, false); 通过函数调用完成分配 一些函数当执行完毕之后,同样存在对象分配的情况发生。 var d = new Date(); var e = document.createElement('div'); // 分配一个 DOM 元素 一些方法会分配新值或者对象。 var s = "azerty"; var s2 = s.substr(0, 3); // s2 是一个新的字符串 // 由于字符串是不变的,javascript会为[0, 3]范围的内容创建一个新的字符串 var a = ["ouais ouais", "nan nan"]; var a2 = ["generation", "nan nan"]; var a3 = a.concat(a2); // 把 a 和 a2 结合在一起,产生一个新的数组 对值的使用 对值的使用,其实也就是对分配后的内存执行读写操作。这些操作包括:对变量或者对象的属性进行读写操作,或者向函数传递参数。 当不再需要的时候,释放内存 绝大多数内存管理的问题都发生在这个阶段。最难做的事情是,如何判定分配的内存不再需要。这往往需要开发者做出判定,程序在什么时候不再需要内存,并释放他所占资源。 高级语言的解析器中嵌入了一个叫做“垃圾收集器”的程序,他的工作是用来跟踪内存的分配和使用,判定内存是否被需要,在不再需要的时候执行资源释放操作。他只能获得一个近似值,因为判断一个内存是否被需要,这是个不确定的问题(不能通过一种算法解决)。 垃圾回收 正如上文所述,我们无法准确的做到自动判定“内存不再需要”。所以,垃圾回收对该问题的解决方案有局限性。本节将解释必要的概念,了解主要的垃圾收集算法和它们的局限性。 引用 垃圾回收中一个主要的概念是引用。在内存管理中,当一个对象无论是显式的还是隐式的使用了另外一个对象,我们就说他引用了另外一个对象。例如,javascript对象存在一个隐式的指向原型的引用,还有显式指向他的属性值的引用。 在这里,对象的概念超出了javascript传统意义上对象的概念,他还包括函数作用域和全局作用域。 使用引用计数算法的垃圾回收 下面要介绍的是一种最理想化的算法,引入了 “对象不再需要” 和 “没有其他对象引用该对象” 的概念。当该对象的引用指针变为0的时候,就认为他可以被回收。 例子: var o = { a: { b:2 } }; // 创建了两个对象. 一个对象(a)被另外一个对象(o引用的对象)引用,并把a作为他的属性 // 该对象又被变量o引用 // 很明显,这时没有对象能被回收 var o2 = o; // 变量 o2 再次引用了该对象 o = 1; // o 不再引用该对象,只有o2还在引用该对象 var oa = o2.a; // oa引用 o2 的属性对象 a // 该对象被其他两个对象引用,分别是o2的属性a和oa变量 o2 = "yo"; // 该对象已经不再被其他对象引用了,但是他的属性a任然被oa变量引用,所以他还不能被释放 oa = null; // 现在属性a也不再被别的对象引用,该对象可以被回收了 限制:循环 该算法有其局限性,当一个对象引用另外一个对象,当形成循环引用时,即时他们不再被需要了,垃圾收集器也不会回收他们。 function f(){ var o = {}; var o2 = {}; o.a = o2; // o 引用 o2 o2.a = o; // o2 引用 o return "azerty"; } f(); // 两个对象被创建,并形成相互引用 // 函数调用结束之后,他们不会脱离函数作用域,虽然他们不会被使用,但不会被释放 // 这是因为,引用计数的算法判定只要对象存在被引用的情况,那么就不能对其执行垃圾回收 现实中的例子 ie6、7中,在dom对象上使用引用计数的算法,这里会存在内存泄露的问题。 var div = document.createElement("div"); div.onclick = function(){ doSomething(); }; // div 通过 click 属性引用了事件处理程序 // 当事件处理函数中访问了div变量的时候,会形成循环引用,将导致两个对象都不会被回收,造成内存泄露 标记 - 清除算法 他引入了“对象不再需要”和“对象不可访问(对象不可达)”的概念。该算法假设有一系列的根对象(javascript中的根对象就是全局对象),每隔一段时间,垃圾收集器就会从根对象开始,遍历所以他引用的对象,然后再遍历引用对象引用的对象,以此类推。使用这种方式,垃圾收集器可以获得所有可访问的对象,回收那些不可访问的对象。 这种算法比之前的算法好些,0引用的对象会被设置为不可访问对象,同时他也避免了循环引用造成的困恼。 截止2012年,大多数现代浏览器使用的是这种“标记-清除算法”的垃圾回收器。JavaScript垃圾收集领域(代/增量/并发/并行的垃圾收集),在过去的几年改善了与之相关的算法,但是垃圾收集算法本身(标记-清除算法)和“如何判定一个对象不再需要”并没有得以改善。 周期不再是一个问题 在第一个例子中,函数调用结束之后,这两个对象不会被全局对象引用,也不会被全局对象引用的对象引用。因此,他们会被javascript垃圾回收器标记为不可访问对象。这种事情同样也发生在第二个例子中,当div和事件处理函数被垃圾回收器标记为不可访问,他们就会被释放掉。 限制:对象需要明确的标记为不可访问 这种标记的方法存在局限,但是我们在编程中被没有接触到他,所以我们很少关心垃圾回收相关的内容。 原文地址:https://developer.mozilla.org/en-US/docs/JavaScript/Memory_Management
本文将深入的研究当你输入一个网址的时候,后台到底发生了一件件什么样的事~ 1. 首先嘛,你得在浏览器里输入网址: 2. 浏览器查找域名的IP地址 导航的第一步是通过访问的域名找出其IP地址。DNS查找过程如下: 浏览器缓存 – 浏览器会缓存DNS记录一段时间。 有趣的是,操作系统没有告诉浏览器储存DNS记录的时间,这样不同浏览器会储存个自固定的一个时间(2分钟到30分钟不等)。 系统缓存 – 如果在浏览器缓存里没有找到需要的记录,浏览器会做一个系统调用(windows里是gethostbyname)。这样便可获得系统缓存中的记录。 路由器缓存 – 接着,前面的查询请求发向路由器,它一般会有自己的DNS缓存。 ISP DNS 缓存 – 接下来要check的就是ISP缓存DNS的服务器。在这一般都能找到相应的缓存记录。 递归搜索 – 你的ISP的DNS服务器从跟域名服务器开始进行递归搜索,从.com顶级域名服务器到Facebook的域名服务器。一般DNS服务器的缓存中会有.com域名服务器中的域名,所以到顶级服务器的匹配过程不是那么必要了。 DNS递归查找如下图所示: DNS有一点令人担忧,这就是像wikipedia.org 或者 facebook.com这样的整个域名看上去只是对应一个单独的IP地址。还好,有几种方法可以消除这个瓶颈: 循环 DNS 是DNS查找时返回多个IP时的解决方案。举例来说,Facebook.com实际上就对应了四个IP地址。 负载平衡器 是以一个特定IP地址进行侦听并将网络请求转发到集群服务器上的硬件设备。 一些大型的站点一般都会使用这种昂贵的高性能负载平衡器。 地理 DNS 根据用户所处的地理位置,通过把域名映射到多个不同的IP地址提高可扩展性。这样不同的服务器不能够更新同步状态,但映射静态内容的话非常好。 Anycast 是一个IP地址映射多个物理主机的路由技术。 美中不足,Anycast与TCP协议适应的不是很好,所以很少应用在那些方案中。 大多数DNS服务器使用Anycast来获得高效低延迟的DNS查找。 3. 浏览器给web服务器发送一个HTTP请求 因为像Facebook主页这样的动态页面,打开后在浏览器缓存中很快甚至马上就会过期,毫无疑问他们不能从中读取。 所以,浏览器将把一下请求发送到Facebook所在的服务器: GET http://facebook.com/ HTTP/1.1 Accept: application/x-ms-application, image/jpeg, application/xaml+xml, [...] User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; [...] Accept-Encoding: gzip, deflate Connection: Keep-Alive Host: facebook.com Cookie: datr=1265876274-[...]; locale=en_US; lsd=WW[...]; c_user=2101[...] GET 这个请求定义了要读取的URL: “http://facebook.com/”。 浏览器自身定义 (User-Agent 头), 和它希望接受什么类型的相应 (Accept and Accept-Encoding 头).Connection头要求服务器为了后边的请求不要关闭TCP连接。 请求中也包含浏览器存储的该域名的cookies。可能你已经知道,在不同页面请求当中,cookies是与跟踪一个网站状态相匹配的键值。这样cookies会存储登录用户名,服务器分配的密码和一些用户设置等。Cookies会以文本文档形式存储在客户机里,每次请求时发送给服务器。 用来看原始HTTP请求及其相应的工具很多。作者比较喜欢使用fiddler,当然也有像FireBug这样其他的工具。这些软件在网站优化时会帮上很大忙。 除了获取请求,还有一种是发送请求,它常在提交表单用到。发送请求通过URL传递其参数(e.g.: http://robozzle.com/puzzle.aspx?id=85)。发送请求在请求正文头之后发送其参数。 像“http://facebook.com/”中的斜杠是至关重要的。这种情况下,浏览器能安全的添加斜杠。而像“http: //example.com/folderOrFile”这样的地址,因为浏览器不清楚folderOrFile到底是文件夹还是文件,所以不能自动添加 斜杠。这时,浏览器就不加斜杠直接访问地址,服务器会响应一个重定向,结果造成一次不必要的握手。 4. facebook服务的永久重定向响应 图中所示为Facebook服务器发回给浏览器的响应: HTTP/1.1 301 Moved Permanently Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Expires: Sat, 01 Jan 2000 00:00:00 GMT Location: http://www.facebook.com/ P3P: CP="DSP LAW" Pragma: no-cache Set-Cookie: made_write_conn=deleted; expires=Thu, 12-Feb-2009 05:09:50 GMT; path=/; domain=.facebook.com; httponly Content-Type: text/html; charset=utf-8 X-Cnection: close Date: Fri, 12 Feb 2010 05:09:51 GMT Content-Length: 0 服务器给浏览器响应一个301永久重定向响应,这样浏览器就会访问“http://www.facebook.com/” 而非“http://facebook.com/”。 为什么服务器一定要重定向而不是直接发会用户想看的网页内容呢?这个问题有好多有意思的答案。 其中一个原因跟搜索引擎排名有 关。你看,如果一个页面有两个地址,就像http://www.igoro.com/ 和http://igoro.com/,搜索引擎会认为它们是两个网站,结果造成每一个的搜索链接都减少从而降低排名。而搜索引擎知道301永久重定向是 什么意思,这样就会把访问带www的和不带www的地址归到同一个网站排名下。 还有一个是用不同的地址会造成缓存友好性变差。当一个页面有好几个名字时,它可能会在缓存里出现好几次。 5. 浏览器跟踪重定向地址 现在,浏览器知道了“http://www.facebook.com/”才是要访问的正确地址,所以它会发送另一个获取请求: GET http://www.facebook.com/ HTTP/1.1 Accept: application/x-ms-application, image/jpeg, application/xaml+xml, [...] Accept-Language: en-US User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; [...] Accept-Encoding: gzip, deflate Connection: Keep-Alive Cookie: lsd=XW[...]; c_user=21[...]; x-referer=[...] Host: www.facebook.com 头信息以之前请求中的意义相同。 6. 服务器“处理”请求 服务器接收到获取请求,然后处理并返回一个响应。 这表面上看起来是一个顺向的任务,但其实这中间发生了很多有意思的东西- 就像作者博客这样简单的网站,何况像facebook那样访问量大的网站呢! Web 服务器软件web服务器软件(像IIS和阿帕奇)接收到HTTP请求,然后确定执行什么请求处理来处理它。请求处理就是一个能够读懂请求并且能生成HTML来进行响应的程序(像ASP.NET,PHP,RUBY...)。 举 个最简单的例子,需求处理可以以映射网站地址结构的文件层次存储。像http://example.com/folder1/page1.aspx这个地 址会映射/httpdocs/folder1/page1.aspx这个文件。web服务器软件可以设置成为地址人工的对应请求处理,这样 page1.aspx的发布地址就可以是http://example.com/folder1/page1。 请求处理请求处理阅读请求及它的参数和cookies。它会读取也可能更新一些数据,并讲数据存储在服务器上。然后,需求处理会生成一个HTML响应。 所有动态网站都面临一个有意思的难点 -如何存储数据。小网站一半都会有一个SQL数据库来存储数据,存储大量数据和/或访问量大的网站不得不找一些办法把数据库分配到多台机器上。解决方案 有:sharding (基于主键值讲数据表分散到多个数据库中),复制,利用弱语义一致性的简化数据库。 委托工作给批处理是一个廉价保持数据更新的技术。举例来讲,Fackbook得及时更新新闻feed,但数据支持下的“你可能认识的人”功能只需要每晚更新 (作者猜测是这样的,改功能如何完善不得而知)。批处理作业更新会导致一些不太重要的数据陈旧,但能使数据更新耕作更快更简洁。 7. 服务器发回一个HTML响应 图中为服务器生成并返回的响应: HTTP/1.1 200 OK Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Expires: Sat, 01 Jan 2000 00:00:00 GMT P3P: CP="DSP LAW" Pragma: no-cache Content-Encoding: gzip Content-Type: text/html; charset=utf-8 X-Cnection: close Transfer-Encoding: chunked Date: Fri, 12 Feb 2010 09:05:55 GMT 2b3Tn@[...] 整个响应大小为35kB,其中大部分在整理后以blob类型传输。 内容编码头告诉浏览器整个响应体用gzip算法进行压缩。解压blob块后,你可以看到如下期望的HTML: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" id="facebook" class=" no_js"> <head> <meta http-equiv="Content-type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-language" content="en" /> ... 关于压缩,头信息说明了是否缓存这个页面,如果缓存的话如何去做,有什么cookies要去设置(前面这个响应里没有这点)和隐私信息等等。 请注意报头中把Content-type设置为“text/html”。报头让浏览器将该响应内容以HTML形式呈现,而不是以文件形式下载它。浏览器会根据报头信息决定如何解释该响应,不过同时也会考虑像URL扩展内容等其他因素。 8. 浏览器开始显示HTML 在浏览器没有完整接受全部HTML文档时,它就已经开始显示这个页面了: 9. 浏览器发送获取嵌入在HTML中的对象 在浏览器显示HTML时,它会注意到需要获取其他地址内容的标签。这时,浏览器会发送一个获取请求来重新获得这些文件。 下面是几个我们访问facebook.com时需要重获取的几个URL: 图片http://static.ak.fbcdn.net/rsrc.php/z12E0/hash/8q2anwu7.gifhttp://static.ak.fbcdn.net/rsrc.php/zBS5C/hash/7hwy7at6.gif… CSS 式样表http://static.ak.fbcdn.net/rsrc.php/z448Z/hash/2plh8s4n.csshttp://static.ak.fbcdn.net/rsrc.php/zANE1/hash/cvtutcee.css… JavaScript 文件http://static.ak.fbcdn.net/rsrc.php/zEMOA/hash/c8yzb6ub.jshttp://static.ak.fbcdn.net/rsrc.php/z6R9L/hash/cq2lgbs8.js… 这些地址都要经历一个和HTML读取类似的过程。所以浏览器会在DNS中查找这些域名,发送请求,重定向等等... 但不像动态页面那样,静态文件会允许浏览器对其进行缓存。有的文件可能会不需要与服务器通讯,而从缓存中直接读取。服务器的响应中包含了静态文件保存的期限 信息,所以浏览器知道要把它们缓存多长时间。还有,每个响应都可能包含像版本号一样工作的ETag头(被请求变量的实体值),如果浏览器观察到文件的版本 ETag信息已经存在,就马上停止这个文件的传输。 试着猜猜看“fbcdn.net”在地址中代表什么?聪明的答案是"Facebook内容分发网络"。Facebook利用内容分发网络(CDN)分发像图片,CSS表和JavaScript文件这些静态文件。所以,这些文件会在全球很多CDN的数据中心中留下备份。 静态内容往往代表站点的带宽大小,也能通过CDN轻松的复制。通常网站会使用第三方的CDN。例如,Facebook的静态文件由最大的CDN提供商Akamai来托管。 举例来讲,当你试着ping static.ak.fbcdn.net的时候,可能会从某个akamai.net服务器上获得响应。有意思的是,当你同样再ping一次的时候,响应的服务器可能就不一样,这说明幕后的负载平衡开始起作用了。 10. 浏览器发送异步(AJAX)请求 在Web 2.0伟大精神的指引下,页面显示完成后客户端仍与服务器端保持着联系。 以 Facebook聊天功能为例,它会持续与服务器保持联系来及时更新你那些亮亮灰灰的好友状态。为了更新这些头像亮着的好友状态,在浏览器中执行的 JavaScript代码会给服务器发送异步请求。这个异步请求发送给特定的地址,它是一个按照程式构造的获取或发送请求。还是在Facebook这个例 子中,客户端发送给http://www.facebook.com/ajax/chat/buddy_list.php一个发布请求来获取你好友里哪个 在线的状态信息。 提起这个模式,就必须要讲讲"AJAX"-- “异步JavaScript 和 XML”,虽然服务器为什么用XML格式来进行响应也没有个一清二白的原因。再举个例子吧,对于异步请求,Facebook会返回一些JavaScript的代码片段。 除了其他,fiddler这个工具能够让你看到浏览器发送的异步请求。事实上,你不仅可以被动的做为这些请求的看客,还能主动出击修改和重新发送它们。AJAX请求这么容易被蒙,可着实让那些计分的在线游戏开发者们郁闷的了。(当然,可别那样骗人家~) Facebook聊天功能提供了关于AJAX一个有意思的问题案例:把数据从服务器端推送到客户端。因为HTTP是一个请求-响应协议,所以聊天服务器不能把新消息发给客户。取而代之的是客户端不得不隔几秒就轮询下服务器端看自己有没有新消息。 这些情况发生时长轮询是个减轻服务器负载挺有趣的技术。如果当被轮询时服务器没有新消息,它就不理这个客户端。而当尚未超时的情况下收到了该客户的新消息,服务器就会找到未完成的请求,把新消息做为响应返回给客户端。 原文:http://igoro.com/archive/what-really-happens-when-you-navigate-to-a-url/
原文地址:http://blog.jobbole.com/9189/ 让Web开发人员的生活尽可能轻松和高效,这是Firefox一直坚持的目标之一。通过提供工具和可扩展的web浏览器,让开发人员能够创造出令人惊奇的东西。 Firefox自带的开发工具 我们致力于开发一系列包含在Firefox内的开发工具。它们在 Developer Tools in Firefox Aurora 10 中有更详细的描述,有了它们我们就能做出一些很有趣的事。 我们对大量的用户接口和编码方式进行测试和评估,从而找出在页面中最理想的编码方式。如果你安装了Firefox Aurora,你现在就可以试试!让我们知道你的想法。 继续关注本博客,我们会持续更新自带开发工具的进展和功能。 Firebug 目前为止,浏览器中最著名的web开发工具当属Firebug,毋庸置疑,在相当长的一段时间里,它将成为web开发与调试的标准。Firebug还拥有许多强大的功能,包括大量的插件(见下文)。 但是有件事你需要明白,那就是如果你安装了过多的Firebug插件,它将占用大量内存,所以要选择适合自己的。(编注:推荐阅读 《Web开发者应掌握的12个Firebug技巧》) 一、针对web开发者的Firefox插件 多年来,已经有很多插件能够帮助web开发者最大限度的利用Firefox。下面将列出其中最著名、使用最多的,如果我们疏漏了什么,请在评论中告诉我们! Accessibility Evaluation Toolbar为web开发者提供测试web资源的辅助功能。 All in One SidebarAiOS可以让你打开多个窗口作为侧边栏,并能在它们之间快速切换。有了它,你就可以告别杂乱无章的窗口了!除了书签和历史记录,它还可以在侧边栏中打开下载,插件等对话框。 Cache Status轻松查看和管理缓存。 CacheToggle轻轻一点,就能禁用(或清除)浏览器缓存。 Colorzilla高级取色器,拾色器,渐变色生成器和其他一些功能。 Console2Console2可能会成为下一代错误控制台,从而取代JavaScript控制台。从v0.5版本开始就有控制台过滤器插件,之前只能在Console2网站上获取。 CSS ReloaderCSS Reloader可以让你重载任何网站的CSS,而不需要重载页面。 DOM InspectorDOM Inspector可以用来审阅、编辑web文档或XUL程序中使用的DOM。在双窗口中以不用的视图显示文档及内部节点,并控制DOM。 Firefogg可对Firefox中的视频和音频进行编码。利用Firefogg你可以把大部分媒体文件编码为Ogg或WebM。Firefogg还提供API来把编码过程整合到上传进程中。 FireFTPFireFTP是Firefox上一款免费、安全、跨平台的FTP/SFTP客户端,可以简单、直观的连接到FTP/SFTP服务器上。 FireUnit一款JavaScript单元测试插件程序。 FoxGuide显示水平和垂直参考线,跟你在PhotoShop里用的一样。有助于改善布局,把元素放在适当的网格中,对称排布元素,构造出一个设计。 FoxyProxy StandardFoxyProxy是一款高级代理管理工具,完全替代了Firefox本身有限的代理功能。它比SwitchProxy、ProxyButton、 QuickProxy、 xyzproxy、 ProxyTex、 TorButton等拥有更多的功能。 Geolocater把你定位到你想要的位置。 GreaseMonkey通过使用少量JavaScript,可以自定义网页的显示方式。 HTML ValidatorHTML Validator是一个Mozilla插件,它在Firefox和Mozilla中添加了HTML验证。可以在图标上看到一个HTML页面的错误数量。 HttpFox一个用于Firefox的HTTP分析器插件。 iMacros for Firefox是Firefox自动化。记录并重播重复工作。如果你喜欢Firefox浏览器,却对重复的任务(例如,每天访问相同的网站,填写表单,记住密码等)感到厌烦的话,iMacros for Firefox便是你梦寐以求的解决方案! Jenkins Build MonitorMonitor Jenkins(http://jenkins-ci.org)在Firefox状态栏中创建并显示当前状态。 jQuery extension内嵌于浏览器的jQuery和jQuery UI。 JSONovich以简单、低调的视图漂亮的显示出浏览器中JSON的内容。 JSONView显示浏览器中的JSON文档。 JSView如果你正在访问的网站包含了任何外部js/css文件,图标上就会显示“SS”、”JS”或者都有。点击文件名就能查看各个文件。文件会在新窗口中打开。 Link Widgets通过在工具栏按钮上添加首页、上一页、下一页、尾页来简化页面导航(例如在线漫画、论坛或者技术规范,比如:HTML4建议)。 Live HTTP Headers在浏览网页时查看HTTP头信息。 LiveReloadLiveReload能在文件发生改变时刷新网页。 MeasureIt可以在任何网页上显示标尺,用来检查宽度、高度,或者以像素为单位对其页面元素。 Modify Headers能够添加、修改、过滤发送给web服务器的HTTP报头,这个插件特别有助于移动web开发、HTTP测试和隐私保护。 NoScript浏览器上最好的安全插件。只有你信任站点上的活动内容才能运行,保护你免受跨站脚本攻击和点击劫持攻击。 PageSpeedPage Speed是由Google发起的开源项目,旨在通过应用web性能最佳实例来帮助开发者优化他们的网页。 PixelZoomerPixelZoomer能够对当前网站进行截图,并提供多种像素分析工具。你可以放大网站(最大3200%),测量间距并用取色器取色。 Pixlr Grabber抓取屏幕和网页图片变得轻而易举。有了Pixlr Grabber,只需点击右键,你就可以复制、保存、分享甚至编辑你的最终抓取效果——包括所有图片或者背景。 Poster一个与网络服务和其他网络资源交互的开发者工具,可以让你制作HTTP请求,设置实体段和内容类型。允许你与网络服务相互并查看结果。 QuickJava可以在状态栏或者工具栏快速开启或禁用Java、Javascript、Flash、 Silverlight、Images、样式表和代理,而无需打开任何对话框。 Rainbow Color Tools针对web开发者的颜色工具。选色器、取色器+保存颜色,还能通过拖放提取颜色。 Regular Expressions Tester测试正则表达式的工具,带有颜色高亮(包含子匹配项),也是创建表达式的帮手。 Remove Cookie(s) for Site一个非常简单的插件,用来清除当前打开站点的cookies。它在网页的右键菜单中添加了一个选项,用一键清除Cookies按钮来执行操作,会在状态栏中显示其状态。 Screenshot Pimp只需轻轻一点,就能捕获、抓取、保存、下载或者复制任何你在浏览器中能看到的东西!Screenshot pimp是目前为止Windows和Mac上定制性和用户体验最好的截屏工具栏。 SQLite Manager管理你电脑上所有的SQLite数据库。 Selenium IDE Buttons用一个简单的工具栏按钮来打开selenium IDE。你需要已经安装了selenium IDE:http://seleniumhq.org/projects/ide/ SeoQuake SEO extensionSeoquake是一款FirefoxSEO插件,旨在帮助那些涉及搜索引擎优化(SEO)社会媒体优化(SMO)和网络促销的人。Seoquake可以审查很多重要的SEO参数。 ShowIP在状态栏显示当前页面的IP地址。还允许通过IP(右键单击)和hostname(左键单击)查询自定义信息服务,例如whois、netcraft等。此外,你还可以复制IP到剪贴板。 Stylish用Stylish重构网页,它是一款用户风格管理器。Stylish能让你轻易的为很多网站安装主题和皮肤,像Google、Facebook、YouTuBe、Orkut等,你甚至可以自定义Firefox或者其他程序的主题。 Tamper Data使用tamperdata可以查看、修改HTTP/HTTPS头及post参数。 TAW3只需通过一个简单的按钮,你就能了解一个网站是否能够访问。 Tilt 3D一个基于WebGL的插件,可以让网页变成3D可视化的效果。 Total Validator能够一次执行多种不同的验证。包括外部的、内部的或者本地网页(需要从http://www.totalvalidator.com/downloads/extensiontool.html获取一个桌面工具)。 TryAgainTryAgain会在服务器不能访问时,不断尝试加载网页。 User Agent SwitcherUser Agent Switcher添加了一个菜单和一个工具栏按钮,来控制浏览器的用户代理。 WappalyzerWappalyzer是一个能够发现网站开发技术的浏览器插件。 Web DeveloperWeb Developer会在浏览器中添加各种web开发者工具。 二、Firebug extensions 在一些不同情况下,会用到很多Firebug的插件,在这里会列出其中一部分: AcebugAcebug对Firebug命令行进行语法高亮,并具有模糊自动完成功能。 Code Coverage v2 for Firebug 0.1这个Firebug插件用来报告Jacascript的代码覆盖率。 CSS Usage用来查看使用了哪些CSS规则的Firebug插件。 FirecookieFirecookie是一款可以查看、管理浏览器Cookies的Firebug插件。 Firefinder查找与选中的CSS选择器或XPath表达式相匹配的HTML元素。 FirePHPFirePHP通过一个简单的PHP方法调用,就能让你记录你的Firebug控制台。 FireQuery针对jQuery开发的Firebug插件。 friendly bug让Firebug界面友好,更易于使用。 Inline Code Finder for FirebugInline Code Finder是一个Firebug插件,能发现HTML元素中存在的这些问题:内联JavaScript事件、内联样式、javascript: links。 FireRainbowFirebug中的JacaScript语法高亮工具。 NetExportNetExport是一个Firebug插件,可以导出所有Net panel中收集和计算的数据。创建的文件使用HTTP存档(HAR)格式(基于JSON) Pixel PerfectPixel Perfect是一个Firebug插件,能够让web开发者和设计师轻松的把一张web构图覆盖在开发的HTML页面上。 YSlowYSlos能够分析网页载入缓慢的原因,基于Yahoo!的高性能网站标准。
原文地址:http://www.cnblogs.com/cnwebdeveloper/articles/2234423.html 浏览器可以被认为是使用最广泛的软件,本文将介绍浏览器的工作原理,我们将看到,从你在地址栏输入google.com到你看到google主页过程中都发生了什么。 将讨论的浏览器 今天,有五种主流浏览器——IE、Firefox、Safari、Chrome及Opera。 本文将基于一些开源浏览器的例子——Firefox、 Chrome及Safari,Safari是部分开源的。 根据W3C(World Wide Web Consortium 万维网联盟)的浏览器统计数据,当前(2011年5月),Firefox、Safari及Chrome的市场占有率综合已接近60%。(原文为2009年10月,数据没有太大变化)因此,可以说开源浏览器已经占据了浏览器市场的半壁江山。 浏览器的主要功能 浏览器的主要功能是将用户选择得web资源呈现出来,它需要从服务器请求资源,并将其显示在浏览器窗口中,资源的格式通常是HTML,也包括PDF、image及其他格式。用户用URI(Uniform Resource Identifier 统一资源标识符)来指定所请求资源的位置,在网络一章有更多讨论。 HTML和CSS规范中规定了浏览器解释html文档的方式,由W3C组织对这些规范进行维护,W3C是负责制定web标准的组织。 HTML规范的最新版本是HTML4(http://www.w3.org/TR/html401/),HTML5还在制定中(译注:两年前),最新的CSS规范版本是2(http://www.w3.org/TR/CSS2),CSS3也还正在制定中(译注:同样两年前)。 这些年来,浏览器厂商纷纷开发自己的扩展,对规范的遵循并不完善,这为Web开发者带来了严重的兼容性问题。 但是,浏览器的用户界面则差不多,常见的用户界面元素包括: ◆ 用来输入URI的地址栏 ◆ 前进、后退按钮 ◆ 书签选项 ◆ 用于刷新及暂停当前加载文档的刷新、暂停按钮 ◆ 用于到达主页的主页按钮 奇怪的是,并没有哪个正式公布的规范对用户界面做出规定,这些是多年来各浏览器厂商之间相互模仿和不断改进得结果。 HTML5并没有规定浏览器必须具有的UI元素,但列出了一些常用元素,包括地址栏、状态栏及工具栏。还有一些浏览器有自己专有得功能,比如Firefox得下载管理。更多相关内容将在后面讨论用户界面时介绍。 浏览器的主要构成High Level Structure 浏览器的主要组件包括: 1. 用户界面- 包括地址栏、后退/前进按钮、书签目录等,也就是你所看到的除了用来显示你所请求页面的主窗口之外的其他部分 2. 浏览器引擎- 用来查询及操作渲染引擎的接口 3. 渲染引擎- 用来显示请求的内容,例如,如果请求内容为html,它负责解析html及css,并将解析后的结果显示出来 4. 网络- 用来完成网络调用,例如http请求,它具有平台无关的接口,可以在不同平台上工作 5. UI 后端- 用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口 6. JS解释器- 用来解释执行JS代码 7. 数据存储- 属于持久层,浏览器需要在硬盘中保存类似cookie的各种数据,HTML5定义了web database技术,这是一种轻量级完整的客户端存储技术 图1:浏览器主要组件 需要注意的是,不同于大部分浏览器,Chrome为每个Tab分配了各自的渲染引擎实例,每个Tab就是一个独立的进程。 对于构成浏览器的这些组件,后面会逐一详细讨论。 组件间的通信 Communication between the components Firefox和Chrome都开发了一个特殊的通信结构,后面将有专门的一章进行讨论。 渲染引擎 The rendering engine 渲染引擎的职责就是渲染,即在浏览器窗口中显示所请求的内容。 默认情况下,渲染引擎可以显示html、xml文档及图片,它也可以借助插件(一种浏览器扩展)显示其他类型数据,例如使用PDF阅读器插件,可以显示PDF格式,将由专门一章讲解插件及扩展,这里只讨论渲染引擎最主要的用途——显示应用了CSS之后的html及图片。 渲染引擎 Rendering engines 本文所讨论得浏览器——Firefox、Chrome和Safari是基于两种渲染引擎构建的,Firefox使用Geoko——Mozilla自主研发的渲染引擎,Safari和Chrome都使用webkit。 Webkit是一款开源渲染引擎,它本来是为linux平台研发的,后来由Apple移植到Mac及Windows上,相关内容请参考http://webkit.org。 主流程 The main flow 渲染引擎首先通过网络获得所请求文档的内容,通常以8K分块的方式完成。 下面是渲染引擎在取得内容之后的基本流程: 解析html以构建dom树->构建render树->布局render树->绘制render树 图2:渲染引擎基本流程 渲染引擎开始解析html,并将标签转化为内容树中的dom节点。接着,它解析外部CSS文件及style标签中的样式信息。这些样式信息以及html中的可见性指令将被用来构建另一棵树——render树。 Render树由一些包含有颜色和大小等属性的矩形组成,它们将被按照正确的顺序显示到屏幕上。 Render树构建好了之后,将会执行布局过程,它将确定每个节点在屏幕上的确切坐标。再下一步就是绘制,即遍历render树,并使用UI后端层绘制每个节点。 值得注意的是,这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局render树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。 图3:webkit主流程 图4:Mozilla的Geoko 渲染引擎主流程 从图3和4中可以看出,尽管webkit和Gecko使用的术语稍有不同,他们的主要流程基本相同。Gecko称可见的格式化元素组成的树为frame树,每个元素都是一个frame,webkit则使用render树这个名词来命名由渲染对象组成的树。Webkit中元素的定位称为布局,而Gecko中称为回流。Webkit称利用dom节点及样式信息去构建render树的过程为attachment,Gecko在html和dom树之间附加了一层,这层称为内容接收器,相当制造dom元素的工厂。下面将讨论流程中的各个阶段。 解析 Parsing-general 既然解析是渲染引擎中一个非常重要的过程,我们将稍微深入的研究它。首先简要介绍一下解析。 解析一个文档即将其转换为具有一定意义的结构——编码可以理解和使用的东西。解析的结果通常是表达文档结构的节点树,称为解析树或语法树。 例如,解析“2+3-1”这个表达式,可能返回这样一棵树。 图5:数学表达式树节点 文法 Grammars 解析基于文档依据的语法规则——文档的语言或格式。每种可被解析的格式必须具有由词汇及语法规则组成的特定的文法,称为上下文无关文法。人类语言不具有这一特性,因此不能被一般的解析技术所解析。 解析器-词法分析器 Parser-Lexer combination 解析可以分为两个子过程——语法分析及词法分析 词法分析就是将输入分解为符号,符号是语言的词汇表——基本有效单元的集合。对于人类语言来说,它相当于我们字典中出现的所有单词。 语法分析指对语言应用语法规则。 解析器一般将工作分配给两个组件——词法分析器(有时也叫分词器)负责将输入分解为合法的符号,解析器则根据语言的语法规则分析文档结构,从而构建解析树,词法分析器知道怎么跳过空白和换行之类的无关字符。 图6:从源文档到解析树 解析过程是迭代的,解析器从词法分析器处取道一个新的符号,并试着用这个符号匹配一条语法规则,如果匹配了一条规则,这个符号对应的节点将被添加到解析树上,然后解析器请求另一个符号。如果没有匹配到规则,解析器将在内部保存该符号,并从词法分析器取下一个符号,直到所有内部保存的符号能够匹配一项语法规则。如果最终没有找到匹配的规则,解析器将抛出一个异常,这意味着文档无效或是包含语法错误。 转换 Translation 很多时候,解析树并不是最终结果。解析一般在转换中使用——将输入文档转换为另一种格式。编译就是个例子,编译器在将一段源码编译为机器码的时候,先将源码解析为解析树,然后将该树转换为一个机器码文档。 图7:编译流程 解析实例 Parsing example 图5中,我们从一个数学表达式构建了一个解析树,这里定义一个简单的数学语言来看下解析过程。 词汇表:我们的语言包括整数、加号及减号。 语法: 1. 该语言的语法基本单元包括表达式、term及操作符 2. 该语言可以包括多个表达式 3. 一个表达式定义为两个term通过一个操作符连接 4. 操作符可以是加号或减号 5. term可以是一个整数或一个表达式 现在来分析一下“2+3-1”这个输入 第一个匹配规则的子字符串是“2”,根据规则5,它是一个term,第二个匹配的是“2+3”,它符合第2条规则——一个操作符连接两个term,下一次匹配发生在输入的结束处。“2+3-1”是一个表达式,因为我们已经知道“2+3”是一个term,所以我们有了一个term紧跟着一个操作符及另一个term。“2++”将不会匹配任何规则,因此是一个无效输入。 词汇表及语法的定义 词汇表通常利用正则表达式来定义。 例如上面的语言可以定义为: INTEGER:0|[1-9][0-9]* PLUS:+ MINUS:- 正如看到的,这里用正则表达式定义整数。 语法通常用BNF格式定义,我们的语言可以定义为: expression := term operation term operation := PLUS | MINUS term := INTEGER | expression 如果一个语言的文法是上下文无关的,则它可以用正则解析器来解析。对上下文无关文法的一个直观的定义是,该文法可以用BNF来完整的表达。可查看http://en.wikipedia.org/wiki/Context-free_grammar。 解析器类型 Types of parsers 有两种基本的解析器——自顶向下解析及自底向上解析。比较直观的解释是,自顶向下解析,查看语法的最高层结构并试着匹配其中一个;自底向上解析则从输入开始,逐步将其转换为语法规则,从底层规则开始直到匹配高层规则。 来看一下这两种解析器如何解析上面的例子: 自顶向下解析器从最高层规则开始——它先识别出“2+3“,将其视为一个表达式,然后识别出”2+3-1“为一个表达式(识别表达式的过程中匹配了其他规则,但出发点是最高层规则)。 自底向上解析会扫描输入直到匹配了一条规则,然后用该规则取代匹配的输入,直到解析完所有输入。部分匹配的表达式被放置在解析堆栈中。 Stack Input 2 + 3 – 1 term + 3 - 1 term operation 3 – 1 expression - 1 expression operation 1 expression 自底向上解析器称为shift reduce 解析器,因为输入向右移动(想象一个指针首先指向输入开始处,并向右移动),并逐渐简化为语法规则。 自动化解析 Generating parsers automatically 解析器生成器这个工具可以自动生成解析器,只需要指定语言的文法——词汇表及语法规则,它就可以生成一个解析器。创建一个解析器需要对解析有深入的理解,而且手动的创建一个由较好性能的解析器并不容易,所以解析生成器很有用。Webkit使用两个知名的解析生成器——用于创建语法分析器的Flex及创建解析器的Bison(你可能接触过Lex和Yacc)。Flex的输入是一个包含了符号定义的正则表达式,Bison的输入是用BNF格式表示的语法规则。 HTML解析器 HTML Parser HTML解析器的工作是将html标识解析为解析树。 HTML文法定义 The HTML grammar definition W3C组织制定规范定义了HTML的词汇表和语法。 非上下文无关文法 Not a context free grammar 正如在解析简介中提到的,上下文无关文法的语法可以用类似BNF的格式来定义。 不幸的是,所有的传统解析方式都不适用于html(当然我提出它们并不只是因为好玩,它们将用来解析css和js),html不能简单的用解析所需的上下文无关文法来定义。 Html 有一个正式的格式定义——DTD(Document Type Definition 文档类型定义)——但它并不是上下文无关文法,html更接近于xml,现在有很多可用的xml解析器,html有个xml的变体——xhtml,它们间的不同在于,html更宽容,它允许忽略一些特定标签,有时可以省略开始或结束标签。总的来说,它是一种soft语法,不像xml呆板、固执。 显然,这个看起来很小的差异却带来了很大的不同。一方面,这是html流行的原因——它的宽容使web开发人员的工作更加轻松,但另一方面,这也使很难去写一个格式化的文法。所以,html的解析并不简单,它既不能用传统的解析器解析,也不能用xml解析器解析。 HTML DTD Html适用DTD格式进行定义,这一格式是用于定义SGML家族的语言,包括了对所有允许元素及它们的属性和层次关系的定义。正如前面提到的,html DTD并没有生成一种上下文无关文法。 DTD有一些变种,标准模式只遵守规范,而其他模式则包含了对浏览器过去所使用标签的支持,这么做是为了兼容以前内容。最新的标准DTD在http://www.w3.org/TR/html4/strict.dtd DOM 输出的树,也就是解析树,是由DOM元素及属性节点组成的。DOM是文档对象模型的缩写,它是html文档的对象表示,作为html元素的外部接口供js等调用。 树的根是“document”对象。 DOM和标签基本是一一对应的关系,例如,如下的标签: <html> <body> <p> Hello DOM </p> <div><img src=”example.png” /></div> </body> </html> 将会被转换为下面的DOM树: 图8:示例标签对应的DOM树 和html一样,DOM的规范也是由W3C组织制定的。访问http://www.w3.org/DOM/DOMTR,这是使用文档的一般规范。一个模型描述一种特定的html元素,可以在http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.htm 查看html定义。 这里所谓的树包含了DOM节点是说树是由实现了DOM接口的元素构建而成的,浏览器使用已被浏览器内部使用的其他属性的具体实现。 解析算法 The parsing algorithm 正如前面章节中讨论的,hmtl不能被一般的自顶向下或自底向上的解析器所解析。 原因是: 1. 这门语言本身的宽容特性 2. 浏览器对一些常见的非法html有容错机制 3. 解析过程是往复的,通常源码不会在解析过程中发生改变,但在html中,脚本标签包含的“document.write”可能添加标签,这说明在解析过程中实际上修改了输入 不能使用正则解析技术,浏览器为html定制了专属的解析器。 Html5规范中描述了这个解析算法,算法包括两个阶段——符号化及构建树。 符号化是词法分析的过程,将输入解析为符号,html的符号包括开始标签、结束标签、属性名及属性值。 符号识别器识别出符号后,将其传递给树构建器,并读取下一个字符,以识别下一个符号,这样直到处理完所有输入。 图9:HTML解析流程 符号识别算法 The tokenization algorithm 算法输出html符号,该算法用状态机表示。每次读取输入流中的一个或多个字符,并根据这些字符转移到下一个状态,当前的符号状态及构建树状态共同影响结果,这意味着,读取同样的字符,可能因为当前状态的不同,得到不同的结果以进入下一个正确的状态。 这个算法很复杂,这里用一个简单的例子来解释这个原理。 基本示例——符号化下面的html: <html> <body> Hello world </body> </html> 初始状态为“Data State”,当遇到“<”字符,状态变为“Tag open state”,读取一个a-z的字符将产生一个开始标签符号,状态相应变为“Tag name state”,一直保持这个状态直到读取到“>”,每个字符都附加到这个符号名上,例子中创建的是一个html符号。 当读取到“>”,当前的符号就完成了,此时,状态回到“Data state”,“<body>”重复这一处理过程。到这里,html和body标签都识别出来了。现在,回到“Data state”,读取“Hello world”中的字符“H”将创建并识别出一个字符符号,这里会为“Hello world”中的每个字符生成一个字符符号。 这样直到遇到“</body>”中的“<”。现在,又回到了“Tag open state”,读取下一个字符“/”将创建一个闭合标签符号,并且状态转移到“Tag name state”,还是保持这一状态,直到遇到“>”。然后,产生一个新的标签符号并回到“Data state”。后面的“</html>”将和“</body>”一样处理。 图10:符号化示例输入 树的构建算法 Tree construction algorithm 在树的构建阶段,将修改以Document为根的DOM树,将元素附加到树上。每个由符号识别器识别生成的节点将会被树构造器进行处理,规范中定义了每个符号相对应的Dom元素,对应的Dom元素将会被创建。这些元素除了会被添加到Dom树上,还将被添加到开放元素堆栈中。这个堆栈用来纠正嵌套的未匹配和未闭合标签,这个算法也是用状态机来描述,所有的状态采用插入模式。 来看一下示例中树的创建过程: <html> <body> Hello world </body> </html> 构建树这一阶段的输入是符号识别阶段生成的符号序列。 首先是“initial mode”,接收到html符号后将转换为“before html”模式,在这个模式中对这个符号进行再处理。此时,创建了一个HTMLHtmlElement元素,并将其附加到根Document对象上。 状态此时变为“before head”,接收到body符号时,即使这里没有head符号,也将自动创建一个HTMLHeadElement元素并附加到树上。 现在,转到“in head”模式,然后是“after head”。到这里,body符号会被再次处理,将创建一个HTMLBodyElement并插入到树中,同时,转移到“in body”模式。 然后,接收到字符串“Hello world”的字符符号,第一个字符将导致创建并插入一个text节点,其他字符将附加到该节点。 接收到body结束符号时,转移到“after body”模式,接着接收到html结束符号,这个符号意味着转移到了“after after body”模式,当接收到文件结束符时,整个解析过程结束。 图11:示例html树的构建过程 解析结束时的处理 Action when the parsing is finished 在这个阶段,浏览器将文档标记为可交互的,并开始解析处于延时模式中的脚本——这些脚本在文档解析后执行。 文档状态将被设置为完成,同时触发一个load事件。 Html5规范中有符号化及构建树的完整算法(http://www.w3.org/TR/html5/syntax.html#html-parser)。 浏览器容错 Browsers error tolerance 你从来不会在一个html页面上看到“无效语法”这样的错误,浏览器修复了无效内容并继续工作。 以下面这段html为例: <html> <mytag> </mytag> <div> <p> </div> Really lousy HTML </p> </html> 这段html违反了很多规则(mytag不是合法的标签,p及div错误的嵌套等等),但是浏览器仍然可以没有任何怨言的继续显示,它在解析的过程中修复了html作者的错误。 浏览器都具有错误处理的能力,但是,另人惊讶的是,这并不是html最新规范的内容,就像书签及前进后退按钮一样,它只是浏览器长期发展的结果。一些比较知名的非法html结构,在许多站点中出现过,浏览器都试着以一种和其他浏览器一致的方式去修复。 Html5规范定义了这方面的需求,webkit在html解析类开始部分的注释中做了很好的总结。 解析器将符号化的输入解析为文档并创建文档,但不幸的是,我们必须处理很多没有很好格式化的html文档,至少要小心下面几种错误情况。 1. 在未闭合的标签中添加明确禁止的元素。这种情况下,应该先将前一标签闭合 2. 不能直接添加元素。有些人在写文档的时候会忘了中间一些标签(或者中间标签是可选的),比如HTML HEAD BODY TR TD LI等 3. 想在一个行内元素中添加块状元素。关闭所有的行内元素,直到下一个更高的块状元素 4. 如果这些都不行,就闭合当前标签直到可以添加该元素。 下面来看一些webkit容错的例子: </br>替代<br> 一些网站使用</br>替代<br>,为了兼容IE和Firefox,webkit将其看作<br>。 代码: if (t->isCloseTag(brTag) && m_document->inCompatMode()) { reportError(MalformedBRError); t->beginTag = true; } Note-这里的错误处理在内部进行,用户看不到。 迷路的表格 这指一个表格嵌套在另一个表格中,但不在它的某个单元格内。 比如下面这个例子: <table> <table> <tr><td>inner table</td></tr> </table> <tr><td>outer table</td></tr> </table> webkit将会将嵌套的表格变为两个兄弟表格: <table> <tr><td>outer table</td></tr> </table> <table> <tr><td>inner table</td></tr> </table> 代码: if (m_inStrayTableContent && localName == tableTag) popBlock(tableTag); webkit使用堆栈存放当前的元素内容,它将从外部表格的堆栈中弹出内部的表格,则它们变为了兄弟表格。 嵌套的表单元素 用户将一个表单嵌套到另一个表单中,则第二个表单将被忽略。 代码: if (!m_currentFormElement) { m_currentFormElement = new HTMLFormElement(formTag, m_document); } 太深的标签继承 www.liceo.edu.mx是一个由嵌套层次的站点的例子,最多只允许20个相同类型的标签嵌套,多出来的将被忽略。 代码: bool HTMLParser::allowNestedRedundantTag(const AtomicString& tagName) { unsigned i = 0; for (HTMLStackElem* curr = m_blockStack; i < cMaxRedundantTagDepth && curr && curr->tagName == tagName; currcurr = curr->next, i++) { } return i != cMaxRedundantTagDepth; } 放错了地方的html、body闭合标签 又一次不言自明。 支持不完整的html。我们从来不闭合body,因为一些愚蠢的网页总是在还未真正结束时就闭合它。我们依赖调用end方法去执行关闭的处理。 代码: if (t->tagName == htmlTag || t->tagName == bodyTag ) return; 所以,web开发者要小心了,除非你想成为webkit容错代码的范例,否则还是写格式良好的html吧。 CSS解析 CSS parsing 还记得简介中提到的解析的概念吗,不同于html,css属于上下文无关文法,可以用前面所描述的解析器来解析。Css规范定义了css的词法及语法文法。 看一些例子: 每个符号都由正则表达式定义了词法文法(词汇表): comment ///*[^*]*/*+([^/*][^*]*/*+)*// num [0-9]+|[0-9]*"."[0-9]+ nonascii [/200-/377] nmstart [_a-z]|{nonascii}|{escape} nmchar [_a-z0-9-]|{nonascii}|{escape} name {nmchar}+ ident {nmstart}{nmchar}* “ident”是识别器的缩写,相当于一个class名,“name”是一个元素id(用“#”引用)。 语法用BNF进行描述: ruleset : selector [ ',' S* selector ]* '{' S* declaration [ ';' S* declaration ]* '}' S* ; selector : simple_selector [ combinator selector | S+ [ combinator selector ] ] ; simple_selector : element_name [ HASH | class | attrib | pseudo ]* | [ HASH | class | attrib | pseudo ]+ ; class : '.' IDENT ; element_name : IDENT | '*' ; attrib : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S* [ IDENT | STRING ] S* ] ']' ; pseudo : ':' [ IDENT | FUNCTION S* [IDENT S*] ')' ] ; 说明:一个规则集合有这样的结构 div.error , a.error { color:red; font-weight:bold; } div.error和a.error时选择器,大括号中的内容包含了这条规则集合中的规则,这个结构在下面的定义中正式的定义了: ruleset : selector [ ',' S* selector ]* '{' S* declaration [ ';' S* declaration ]* '}' S* ; 这说明,一个规则集合具有一个或是可选个数的多个选择器,这些选择器以逗号和空格(S表示空格)进行分隔。每个规则集合包含大括号及大括号中的一条或多条以分号隔开的声明。声明和选择器在后面进行定义。 Webkit CSS 解析器 Webkit CSS parser Webkit使用Flex和Bison解析生成器从CSS语法文件中自动生成解析器。回忆一下解析器的介绍,Bison创建一个自底向上的解析器,Firefox使用自顶向下解析器。它们都是将每个css文件解析为样式表对象,每个对象包含css规则,css规则对象包含选择器和声明对象,以及其他一些符合css语法的对象。 图12:解析css 脚本解析 Parsing scripts 本章将介绍Javascript。 处理脚本及样式表的顺序 The order of processing scripts and style sheets 脚本 web的模式是同步的,开发者希望解析到一个script标签时立即解析执行脚本,并阻塞文档的解析直到脚本执行完。如果脚本是外引的,则网络必须先请求到这个资源——这个过程也是同步的,会阻塞文档的解析直到资源被请求到。这个模式保持了很多年,并且在html4及html5中都特别指定了。开发者可以将脚本标识为defer,以使其不阻塞文档解析,并在文档解析结束后执行。Html5增加了标记脚本为异步的选项,以使脚本的解析执行使用另一个线程。 预解析 Speculative parsing Webkit和Firefox都做了这个优化,当执行脚本时,另一个线程解析剩下的文档,并加载后面需要通过网络加载的资源。这种方式可以使资源并行加载从而使整体速度更快。需要注意的是,预解析并不改变Dom树,它将这个工作留给主解析过程,自己只解析外部资源的引用,比如外部脚本、样式表及图片。 样式表 Style sheets 样式表采用另一种不同的模式。理论上,既然样式表不改变Dom树,也就没有必要停下文档的解析等待它们,然而,存在一个问题,脚本可能在文档的解析过程中请求样式信息,如果样式还没有加载和解析,脚本将得到错误的值,显然这将会导致很多问题,这看起来是个边缘情况,但确实很常见。Firefox在存在样式表还在加载和解析时阻塞所有的脚本,而chrome只在当脚本试图访问某些可能被未加载的样式表所影响的特定的样式属性时才阻塞这些脚本。 渲染树的构造 Render tree construction 当Dom树构建完成时,浏览器开始构建另一棵树——渲染树。渲染树由元素显示序列中的可见元素组成,它是文档的可视化表示,构建这棵树是为了以正确的顺序绘制文档内容。 Firefox将渲染树中的元素称为frames,webkit则用renderer或渲染对象来描述这些元素。 一个渲染对象直到怎么布局及绘制自己及它的children。 RenderObject是Webkit的渲染对象基类,它的定义如下: class RenderObject{ virtual void layout(); virtual void paint(PaintInfo); virtual void rect repaintRect(); Node* node; //the DOM node RenderStyle* style; // the computed style RenderLayer* containgLayer; //the containing z-index layer } 每个渲染对象用一个和该节点的css盒模型相对应的矩形区域来表示,正如css2所描述的那样,它包含诸如宽、高和位置之类的几何信息。盒模型的类型受该节点相关的display样式属性的影响(参考样式计算章节)。下面的webkit代码说明了如何根据display属性决定某个节点创建何种类型的渲染对象。 RenderObject* RenderObject::createObject(Node* node, RenderStyle* style) { Document* doc = node->document(); RenderArena* arena = doc->renderArena(); ... RenderObject* o = 0; switch (style->display()) { case NONE: break; case INLINE: o = new (arena) RenderInline(node); break; case BLOCK: o = new (arena) RenderBlock(node); break; case INLINE_BLOCK: o = new (arena) RenderBlock(node); break; case LIST_ITEM: o = new (arena) RenderListItem(node); break; ... } return o; } 元素的类型也需要考虑,例如,表单控件和表格带有特殊的框架。 在webkit中,如果一个元素想创建一个特殊的渲染对象,它需要复写“createRenderer”方法,使渲染对象指向不包含几何信息的样式对象。 渲染树和Dom树的关系 The render tree relation to the DOM tree 渲染对象和Dom元素相对应,但这种对应关系不是一对一的,不可见的Dom元素不会被插入渲染树,例如head元素。另外,display属性为none的元素也不会在渲染树中出现(visibility属性为hidden的元素将出现在渲染树中)。 还有一些Dom元素对应几个可见对象,它们一般是一些具有复杂结构的元素,无法用一个矩形来描述。例如,select元素有三个渲染对象——一个显示区域、一个下拉列表及一个按钮。同样,当文本因为宽度不够而折行时,新行将作为额外的渲染元素被添加。另一个多个渲染对象的例子是不规范的html,根据css规范,一个行内元素只能仅包含行内元素或仅包含块状元素,在存在混合内容时,将会创建匿名的块状渲染对象包裹住行内元素。 一些渲染对象和所对应的Dom节点不在树上相同的位置,例如,浮动和绝对定位的元素在文本流之外,在两棵树上的位置不同,渲染树上标识出真实的结构,并用一个占位结构标识出它们原来的位置。 图13:渲染树及对应的Dom树 创建树的流程 The flow of constructing the tree Firefox中,表述为一个监听Dom更新的监听器,将frame的创建委派给Frame Constructor,这个构建器计算样式(参看样式计算)并创建一个frame。 Webkit中,计算样式并生成渲染对象的过程称为attachment,每个Dom节点有一个attach方法,attachment的过程是同步的,调用新节点的attach方法将节点插入到Dom树中。 处理html和body标签将构建渲染树的根,这个根渲染对象对应被css规范称为containing block的元素——包含了其他所有块元素的顶级块元素。它的大小就是viewport——浏览器窗口的显示区域,Firefox称它为viewPortFrame,webkit称为RenderView,这个就是文档所指向的渲染对象,树中其他的部分都将作为一个插入的Dom节点被创建。 样式计算 Style Computation 创建渲染树需要计算出每个渲染对象的可视属性,这可以通过计算每个元素的样式属性得到。 样式包括各种来源的样式表,行内样式元素及html中的可视化属性(例如bgcolor),可视化属性转化为css样式属性。 样式表来源于浏览器默认样式表,及页面作者和用户提供的样式表——有些样式是浏览器用户提供的(浏览器允许用户定义喜欢的样式,例如,在Firefox中,可以通过在Firefox Profile目录下放置样式表实现)。 计算样式的一些困难: 1. 样式数据是非常大的结构,保存大量的样式属性会带来内存问题 2. 如果不进行优化,找到每个元素匹配的规则会导致性能问题,为每个元素查找匹配的规则都需要遍历整个规则表,这个过程有很大的工作量。选择符可能有复杂的结构,匹配过程如果沿着一条开始看似正确,后来却被证明是无用的路径,则必须去尝试另一条路径。 例如,下面这个复杂选择符 div div div div{…} 这意味着规则应用到三个div的后代div元素,选择树上一条特定的路径去检查,这可能需要遍历节点树,最后却发现它只是两个div的后代,并不使用该规则,然后则需要沿着另一条路径去尝试 3. 应用规则涉及非常复杂的级联,它们定义了规则的层次 我们来看一下浏览器如何处理这些问题: 共享样式数据 webkit节点引用样式对象(渲染样式),某些情况下,这些对象可以被节点间共享,这些节点需要是兄弟或是表兄弟节点,并且: 1. 这些元素必须处于相同的鼠标状态(比如不能一个处于hover,而另一个不是) 2. 不能有元素具有id 3. 标签名必须匹配 4. class属性必须匹配 5. 对应的属性必须相同 6. 链接状态必须匹配 7. 焦点状态必须匹配 8. 不能有元素被属性选择器影响 9. 元素不能有行内样式属性 10. 不能有生效的兄弟选择器,webcore在任何兄弟选择器相遇时只是简单的抛出一个全局转换,并且在它们显示时使整个文档的样式共享失效,这些包括+选择器和类似:first-child和:last-child这样的选择器。 Firefox规则树 Firefox rule tree Firefox用两个树用来简化样式计算-规则树和样式上下文树,webkit也有样式对象,但它们并没有存储在类似样式上下文树这样的树中,只是由Dom节点指向其相关的样式。 图14:Firefox样式上下文树 样式上下文包含最终值,这些值是通过以正确顺序应用所有匹配的规则,并将它们由逻辑值转换为具体的值,例如,如果逻辑值为屏幕的百分比,则通过计算将其转化为绝对单位。样式树的使用确实很巧妙,它使得在节点中共享的这些值不需要被多次计算,同时也节省了存储空间。 所有匹配的规则都存储在规则树中,一条路径中的底层节点拥有最高的优先级,这棵树包含了所找到的所有规则匹配的路径(译注:可以取巧理解为每条路径对应一个节点,路径上包含了该节点所匹配的所有规则)。规则树并不是一开始就为所有节点进行计算,而是在某个节点需要计算样式时,才进行相应的计算并将计算后的路径添加到树中。 我们将树上的路径看成辞典中的单词,假如已经计算出了如下的规则树: 假如需要为内容树中的另一个节点匹配规则,现在知道匹配的规则(以正确的顺序)为B-E-I,因为我们已经计算出了路径A-B-E-I-L,所以树上已经存在了这条路径,剩下的工作就很少了。 现在来看一下树如何保存。 结构化 样式上下文按结构划分,这些结构包括类似border或color这样的特定分类的样式信息。一个结构中的所有特性不是继承的就是非继承的,对继承的特性,除非元素自身有定义,否则就从它的parent继承。非继承的特性(称为reset特性)如果没有定义,则使用默认的值。 样式上下文树缓存完整的结构(包括计算后的值),这样,如果底层节点没有为一个结构提供定义,则使用上层节点缓存的结构。 使用规则树计算样式上下文 当为一个特定的元素计算样式时,首先计算出规则树中的一条路径,或是使用已经存在的一条,然后使用路径中的规则去填充新的样式上下文,从样式的底层节点开始,它具有最高优先级(通常是最特定的选择器),遍历规则树,直到填满结构。如果在那个规则节点没有定义所需的结构规则,则沿着路径向上,直到找到该结构规则。 如果最终没有找到该结构的任何规则定义,那么如果这个结构是继承型的,则找到其在内容树中的parent的结构,这种情况下,我们也成功的共享了结构;如果这个结构是reset型的,则使用默认的值。 如果特定的节点添加了值,那么需要做一些额外的计算以将其转换为实际值,然后在树上的节点缓存该值,使它的children可以使用。 当一个元素和它的一个兄弟元素指向同一个树节点时,完整的样式上下文可以被它们共享。 来看一个例子:假设有下面这段html <html> <body> <div class="err" id="div1"> <p>this is a <span class="big"> big error </span> this is also a <span class="big"> very big error</span> error </p> </div> <div class="err" id="div2">another error</div> </body> </html> 以及下面这些规则 1. div {margin:5px;color:black} 2. .err {color:red} 3. .big {margin-top:3px} 4. div span {margin-bottom:4px} 5. #div1 {color:blue} 6. #div2 {color:green} 简化下问题,我们只填充两个结构——color和margin,color结构只包含一个成员-颜色,margin结构包含四边。 生成的规则树如下(节点名:指向的规则) 上下文树如下(节点名:指向的规则节点) 假设我们解析html,遇到第二个div标签,我们需要为这个节点创建样式上下文,并填充它的样式结构。 我们进行规则匹配,找到这个div匹配的规则为1、2、6,我们发现规则树上已经存在了一条我们可以使用的路径1、2,我们只需为规则6新增一个节点添加到下面(就是规则树中的F)。 然后创建一个样式上下文并将其放到上下文树中,新的样式上下文将指向规则树中的节点F。 现在我们需要填充这个样式上下文,先从填充margin结构开始,既然最后一个规则节点没有添加margin结构,沿着路径向上,直到找到缓存的前面插入节点计算出的结构,我们发现B是最近的指定margin值的节点。因为已经有了color结构的定义,所以不能使用缓存的结构,既然color只有一个属性,也就不需要沿着路径向上填充其他属性。计算出最终值(将字符串转换为RGB等),并缓存计算后的结构。 第二个span元素更简单,进行规则匹配后发现它指向规则G,和前一个span一样,既然有兄弟节点指向同一个节点,就可以共享完整的样式上下文,只需指向前一个span的上下文。 因为结构中包含继承自parent的规则,上下文树做了缓存(color特性是继承来的,但Firefox将其视为reset并在规则树中缓存)。 例如,如果我们为一个paragraph的文字添加规则: p {font-family:Verdana;font size:10px;font-weight:bold} 那么这个p在内容树中的子节点div,会共享和它parent一样的font结构,这种情况发生在没有为这个div指定font规则时。 Webkit中,并没有规则树,匹配的声明会被遍历四次,先是应用非important的高优先级属性(之所以先应用这些属性,是因为其他的依赖于它们-比如display),其次是高优先级important的,接着是一般优先级非important的,最后是一般优先级important的规则。这样,出现多次的属性将被按照正确的级联顺序进行处理,最后一个生效。 总结一下,共享样式对象(结构中完整或部分内容)解决了问题1和3,Firefox的规则树帮助以正确的顺序应用规则。 对规则进行处理以简化匹配过程 样式规则有几个来源: ◆ 外部样式表或style标签内的css规则 ◆ 行内样式属性 ◆ html可视化属性(映射为相应的样式规则) 后面两个很容易匹配到元素,因为它们所拥有的样式属性和html属性可以将元素作为key进行映射。 就像前面问题2所提到的,css的规则匹配可能很狡猾,为了解决这个问题,可以先对规则进行处理,以使其更容易被访问。 解析完样式表之后,规则会根据选择符添加一些hash映射,映射可以是根据id、class、标签名或是任何不属于这些分类的综合映射。如果选择符为id,规则将被添加到id映射,如果是class,则被添加到class映射,等等。 这个处理是匹配规则更容易,不需要查看每个声明,我们能从映射中找到一个元素的相关规则,这个优化使在进行规则匹配时减少了95+%的工作量。 来看下面的样式规则: p.error {color:red} #messageDiv {height:50px} div {margin:5px} 第一条规则将被插入class映射,第二条插入id映射,第三条是标签映射。 下面这个html片段: <p class="error">an error occurred </p> <div id=" messageDiv">this is a message</div> 我们首先找到p元素对应的规则,class映射将包含一个“error”的key,找到p.error的规则,div在id映射和标签映射中都有相关的规则,剩下的工作就是找出这些由key对应的规则中哪些确实是正确匹配的。 例如,如果div的规则是 table div {margin:5px} 这也是标签映射产生的,因为key是最右边的选择符,但它并不匹配这里的div元素,因为这里的div没有table祖先。 Webkit和Firefox都会做这个处理。 以正确的级联顺序应用规则 样式对象拥有对应所有可见属性的属性,如果特性没有被任何匹配的规则所定义,那么一些特性可以从parent的样式对象中继承,另外一些使用默认值。 这个问题的产生是因为存在不止一处的定义,这里用级联顺序解决这个问题。 样式表的级联顺序 一个样式属性的声明可能在几个样式表中出现,或是在一个样式表中出现多次,因此,应用规则的顺序至关重要,这个顺序就是级联顺序。根据css2的规范,级联顺序为(从低到高): 1. 浏览器声明 2. 用户声明 3. 作者的一般声明 4. 作者的important声明 5. 用户important声明 浏览器声明是最不重要的,用户只有在声明被标记为important时才会覆盖作者的声明。具有同等级别的声明将根据specifity以及它们被定义时的顺序进行排序。Html可视化属性将被转换为匹配的css声明,它们被视为最低优先级的作者规则。 Specifity Css2规范中定义的选择符specifity如下: ◆ 如果声明来自style属性,而不是一个选择器的规则,则计1,否则计0(=a) ◆ 计算选择器中id属性的数量(=b) ◆ 计算选择器中class及伪类的数量(=c) ◆ 计算选择器中元素名及伪元素的数量(=d) 连接a-b-c-d四个数量(用一个大基数的计算系统)将得到specifity。这里使用的基数由分类中最高的基数定义。例如,如果a为14,可以使用16进制。不同情况下,a为17时,则需要使用阿拉伯数字17作为基数,这种情况可能在这个选择符时发生html body div div …(选择符中有17个标签,一般不太可能)。 一些例子: * {} /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */ li {} /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */ li:first-line {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */ ul li {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */ ul ol+li {} /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */ h1 + *[rel=up]{} /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */ ul ol li.red {} /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */ li.red.level {} /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */ #x34y {} /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */ /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */ 规则排序 规则匹配后,需要根据级联顺序对规则进行排序,webkit先将小列表用冒泡排序,再将它们合并为一个大列表,webkit通过为规则复写“>”操作来执行排序: static bool operator >(CSSRuleData& r1, CSSRuleData& r2) { int spec1 = r1.selector()->specificity(); int spec2 = r2.selector()->specificity(); return (spec1 == spec2) : r1.position() > r2.position() : spec1 > spec2; } 逐步处理 Gradual process webkit使用一个标志位标识所有顶层样式表都已加载,如果在attch时样式没有完全加载,则放置占位符,并在文档中标记,一旦样式表完成加载就重新进行计算。 布局 Layout 当渲染对象被创建并添加到树中,它们并没有位置和大小,计算这些值的过程称为layout或reflow。 Html使用基于流的布局模型,意味着大部分时间,可以以单一的途径进行几何计算。流中靠后的元素并不会影响前面元素的几何特性,所以布局可以在文档中从右向左、自上而下的进行。也存在一些例外,比如html tables。 坐标系统相对于根frame,使用top和left坐标。 布局是一个递归的过程,由根渲染对象开始,它对应html文档元素,布局继续递归的通过一些或所有的frame层级,为每个需要几何信息的渲染对象进行计算。 根渲染对象的位置是0,0,它的大小是viewport-浏览器窗口的可见部分。 所有的渲染对象都有一个layout或reflow方法,每个渲染对象调用需要布局的children的layout方法。 Dirty bit 系统 为了不因为每个小变化都全部重新布局,浏览器使用一个dirty bit系统,一个渲染对象发生了变化或是被添加了,就标记它及它的children为dirty-需要layout。存在两个标识-dirty及children are dirty,children are dirty说明即使这个渲染对象可能没问题,但它至少有一个child需要layout。 全局和增量 layout 当layout在整棵渲染树触发时,称为全局layout,这可能在下面这些情况下发生: 1. 一个全局的样式改变影响所有的渲染对象,比如字号的改变 2. 窗口resize layout也可以是增量的,这样只有标志为dirty的渲染对象会重新布局(也将导致一些额外的布局)。增量layout会在渲染对象dirty时异步触发,例如,当网络接收到新的内容并添加到Dom树后,新的渲染对象会添加到渲染树中。 图20:增量 layout 异步和同步layout 增量layout的过程是异步的,Firefox为增量layout生成了reflow队列,以及一个调度执行这些批处理命令。Webkit也有一个计时器用来执行增量layout-遍历树,为dirty状态的渲染对象重新布局。 另外,当脚本请求样式信息时,例如“offsetHeight”,会同步的触发增量布局。 全局的layout一般都是同步触发。 有些时候,layout会被作为一个初始layout之后的回调,比如滑动条的滑动。 优化 当一个layout因为resize或是渲染位置改变(并不是大小改变)而触发时,渲染对象的大小将会从缓存中读取,而不会重新计算。 一般情况下,如果只有子树发生改变,则layout并不从根开始。这种情况发生在,变化发生在元素自身并且不影响它周围元素,例如,将文本插入文本域(否则,每次击键都将触发从根开始的重排)。 layout过程 layout一般有下面这几个部分: 1. parent渲染对象决定它的宽度 2. parent渲染对象读取chilidren,并: 1. 放置child渲染对象(设置它的x和y) 2. 在需要时(它们当前为dirty或是处于全局layout或者其他原因)调用child渲染对象的layout,这将计算child的高度 3. parent渲染对象使用child渲染对象的累积高度,以及margin和padding的高度来设置自己的高度-这将被parent渲染对象的parent使用 4. 将dirty标识设置为false Firefox使用一个“state”对象(nsHTMLReflowState)做为参数去布局(firefox称为reflow),state包含parent的宽度及其他内容。 Firefox布局的输出是一个“metrics”对象(nsHTMLReflowMetrics)。它包括渲染对象计算出的高度。 宽度计算 渲染对象的宽度使用容器的宽度、渲染对象样式中的宽度及margin、border进行计算。例如,下面这个div的宽度: ◆ <div /> webkit中宽度的计算过程是(RenderBox类的calcWidth方法): ◆ 容器的宽度是容器的可用宽度和0中的最大值,这里的可用宽度为:contentWidth=clientWidth()-paddingLeft()-paddingRight(),clientWidth和clientHeight代表一个对象内部的不包括border和滑动条的大小 ◆ 元素的宽度指样式属性width的值,它可以通过计算容器的百分比得到一个绝对值 ◆ 加上水平方向上的border和padding 到这里是最佳宽度的计算过程,现在计算宽度的最大值和最小值,如果最佳宽度大于最大宽度则使用最大宽度,如果小于最小宽度则使用最小宽度。最后缓存这个值,当需要layout但宽度未改变时使用。 Line breaking 当一个渲染对象在布局过程中需要折行时,则暂停并告诉它的parent它需要折行,parent将创建额外的渲染对象并调用它们的layout。 绘制 Painting 绘制阶段,遍历渲染树并调用渲染对象的paint方法将它们的内容显示在屏幕上,绘制使用UI基础组件,这在UI的章节有更多的介绍。 全局和增量 和布局一样,绘制也可以是全局的-绘制完整的树-或增量的。在增量的绘制过程中,一些渲染对象以不影响整棵树的方式改变,改变的渲染对象使其在屏幕上的矩形区域失效,这将导致操作系统将其看作dirty区域,并产生一个paint事件,操作系统很巧妙的处理这个过程,并将多个区域合并为一个。Chrome中,这个过程更复杂些,因为渲染对象在不同的进程中,而不是在主进程中。Chrome在一定程度上模拟操作系统的行为,表现为监听事件并派发消息给渲染根,在树中查找到相关的渲染对象,重绘这个对象(往往还包括它的children)。 绘制顺序 css2定义了绘制过程的顺序-http://www.w3.org/TR/CSS21/zindex.html。这个就是元素压入堆栈的顺序,这个顺序影响着绘制,堆栈从后向前进行绘制。 一个块渲染对象的堆栈顺序是: 1. 背景色 2. 背景图 3. border 4. children 5. outline Firefox显示列表 Firefox读取渲染树并为绘制的矩形创建一个显示列表,该列表以正确的绘制顺序包含这个矩形相关的渲染对象。 用这样的方法,可以使重绘时只需查找一次树,而不需要多次查找——绘制所有的背景、所有的图片、所有的border等等。 Firefox优化了这个过程,它不添加会被隐藏的元素,比如元素完全在其他不透明元素下面。 Webkit矩形存储 重绘前,webkit将旧的矩形保存为位图,然后只绘制新旧矩形的差集。 动态变化 浏览器总是试着以最小的动作响应一个变化,所以一个元素颜色的变化将只导致该元素的重绘,元素位置的变化将大致元素的布局和重绘,添加一个Dom节点,也会大致这个元素的布局和重绘。一些主要的变化,比如增加html元素的字号,将会导致缓存失效,从而引起整数的布局和重绘。 渲染引擎的线程 渲染引擎是单线程的,除了网络操作以外,几乎所有的事情都在单一的线程中处理,在Firefox和Safari中,这是浏览器的主线程,Chrome中这是tab的主线程。 网络操作由几个并行线程执行,并行连接的个数是受限的(通常是2-6个)。 事件循环 浏览器主线程是一个事件循环,它被设计为无限循环以保持执行过程的可用,等待事件(例如layout和paint事件)并执行它们。下面是Firefox的主要事件循环代码。 while (!mExiting) NS_ProcessNextEvent(thread); CSS2 可视模型 CSS2 visual module 画布 The Canvas 根据CSS2规范,术语canvas用来描述格式化的结构所渲染的空间——浏览器绘制内容的地方。画布对每个维度空间都是无限大的,但浏览器基于viewport的大小选择了一个初始宽度。 根据http://www.w3.org/TR/CSS2/zindex.html的定义,画布如果是包含在其他画布内则是透明的,否则浏览器会指定一个颜色。 CSS盒模型 CSS盒模型描述了矩形盒,这些矩形盒是为文档树中的元素生成的,并根据可视的格式化模型进行布局。每个box包括内容区域(如图片、文本等)及可选的四周padding、border和margin区域。 每个节点生成0-n个这样的box。 所有的元素都有一个display属性,用来决定它们生成box的类型,例如: block-生成块状box inline-生成一个或多个行内box none-不生成box 默认的是inline,但浏览器样式表设置了其他默认值,例如,div元素默认为block。可以访问http://www.w3.org/TR/CSS2/sample.html查看更多的默认样式表示例。 定位策略 Position scheme 这里有三种策略: 1. normal-对象根据它在文档的中位置定位,这意味着它在渲染树和在Dom树中位置一致,并根据它的盒模型和大小进行布局 2. float-对象先像普通流一样布局,然后尽可能的向左或是向右移动 3. absolute-对象在渲染树中的位置和Dom树中位置无关 static和relative是normal,absolute和fixed属于absolute。 在static定位中,不定义位置而使用默认的位置。其他策略中,作者指定位置——top、bottom、left、right。 Box布局的方式由这几项决定:box的类型、box的大小、定位策略及扩展信息(比如图片大小和屏幕尺寸)。 Box类型 Block box:构成一个块,即在浏览器窗口上有自己的矩形 Inline box:并没有自己的块状区域,但包含在一个块状区域内 block一个挨着一个垂直格式化,inline则在水平方向上格式化。 Inline盒模型放置在行内或是line box中,每行至少和最高的box一样高,当box以baseline对齐时——即一个元素的底部和另一个box上除底部以外的某点对齐,行高可以比最高的box高。当容器宽度不够时,行内元素将被放到多行中,这在一个p元素中经常发生。 定位 Position Relative 相对定位——先按照一般的定位,然后按所要求的差值移动。 Floats 一个浮动的box移动到一行的最左边或是最右边,其余的box围绕在它周围。下面这段html: <p> <img src="images/image.gif" width="100" height="100">Lorem ipsum dolor sit amet, consectetuer... </p> 将显示为: Absolute和Fixed 这种情况下的布局完全不顾普通的文档流,元素不属于文档流的一部分,大小取决于容器。Fixed时,容器为viewport(可视区域)。 图17:fixed 注意-fixed即使在文档流滚动时也不会移动。 Layered representation 这个由CSS属性中的z-index指定,表示盒模型的第三个大小,即在z轴上的位置。Box分发到堆栈中(称为堆栈上下文),每个堆栈中靠后的元素将被较早绘制,栈顶靠前的元素离用户最近,当发生交叠时,将隐藏靠后的元素。堆栈根据z-index属性排序,拥有z-index属性的box形成了一个局部堆栈,viewport有外部堆栈,例如: <STYLE type="text/css"> div { position: absolute; left: 2in; top: 2in; } </STYLE> <P> <DIV > </DIV> <DIV > </DIV> </p> 结果是: 虽然绿色div排在红色div后面,可能在正常流中也已经被绘制在后面,但z-index有更高优先级,所以在根box的堆栈中更靠前。
转载请注明原创地址:http://www.cnblogs.com/softlover/archive/2012/11/25/2787555.html 从事过iphone响应式设计的人,一定清楚iphone safari浏览器viewport 缩放的bug。当你将viewport的宽度设计为设备宽度,并将iphone旋转到 landscape 视图的时候,bug就会发生。你可以使用iphone访问demo地址,将视图从 portrait 切换到 landscape,来查看该bug。今天我们将尝试修复这个问题。 问题 下面截图展示了该bug。 解决方案1:使用Meta标签快读修复(demo) 一个快速的修改办法是,为viewport 设置如下属性 maximum-scale=1。副作用是,页面不能被执行缩放操作了。 <meta name="viewport" content="width=device-width; initial-scale=1; maximum-scale=1"> 解决方案2:使用javascript(demo) 为了可用性的考虑,上面的办法是不可行的。我们可以在head中,通过添加下面的javascript脚本来完美的解决这个问题。脚本来源:https://gist.github.com/901295。 <script type="text/javascript"> (function(doc) { var addEvent = 'addEventListener', type = 'gesturestart', qsa = 'querySelectorAll', scales = [1, 1], meta = qsa in doc ? doc[qsa]('meta[name=viewport]') : []; function fix() { meta.content = 'width=device-width,minimum-scale=' + scales[0] + ',maximum-scale=' + scales[1]; doc.removeEventListener(type, fix, true); } if ((meta = meta[meta.length - 1]) && addEvent in doc) { fix(); scales = [.25, 1.6]; doc[addEvent](type, fix, true); } }(document)); </script> 原文地址:http://webdesignerwall.com/tutorials/iphone-safari-viewport-scaling-bug
最近在翻看John Resig的大作《Pro JavaScript Techniques》,里面讲到了如何做javascript的类型判断的问题。文中介绍了两种方式,一种是使用typeof,另一种是使用constructor。略感遗憾的是作为jquery的作者,他尽然没有介绍jquery使用的类型判断方式。不过没有关系,我在这里给大家一起总结下。 在这里我首先像大家推荐一个很好用的在线编辑器:http://jsfiddle.net/。他提供了jquery、mootools、prototype和YUI三个主流js框架的各个版本,当你需要编写简单的js测试程序的时候可以直接使用它。省去了打开编辑软件,创建各种类型文件的步骤。编辑代码之后,点击[Run]按钮,一切搞定。 1.typeof typeof是我们在做类型判断时最常用的方法,他的优点就是简单、好记,缺点是不能很好的判断object、null、array、regexp和自定义对象。 下面是我的测试代码: var str='str'; var arr=['1','2']; var num=1; var bool=true; var obj={name:'test'}; var nullObj=null; var undefinedObj=undefined; var reg=/reg/; function fn(){ alert('this is a function'); } function User(name){ this.name=name; } var user=new User('user'); console.log(typeof str); console.log(typeof arr); console.log(typeof num); console.log(typeof bool); console.log(typeof obj); console.log(typeof nullObj); console.log(typeof undefinedObj); console.log(typeof reg); console.log(typeof fn); console.log(typeof user); 代码运行结果: 2.constructor 现在介绍一种不常使用的方法,对象构造器constructor。他的优点是支持大部分对象类型的判断,特别是对自定义对象的判断;缺点是不能在null和undefined上使用。 测试代码和之前的差不多,区别就是使用XXX.constructor代替了typeof。 var str='str'; var arr=['1','2']; var num=1; var bool=true; var obj={name:'test'}; var nullObj=null; var undefinedObj=undefined; var reg=/reg/; function fn(){ alert('this is a function'); } function User(name){ this.name=name; } var user=new User('user'); console.log(str.constructor); console.log(arr.constructor); console.log(num.constructor); console.log(bool.constructor); console.log(obj.constructor); console.log(reg.constructor); console.log(fn.constructor); console.log(user.constructor); console.log(nullObj.constructor); console.log(undefinedObj.constructor); 运行结果: 运行到 console.log(nullObj.constructor); 的时候,浏览器报错:Uncaught TypeError: Cannot read property 'constructor' of null。类似的问题也发生在console.log(undefinedObj.constructor); 上面:Uncaught TypeError: Cannot read property 'constructor' of undefined。 3.Object.prototype.toString.call() 最后要介绍的是jquery中使用的方式,Object.prototype.toString.call()。优点是支持绝大多数类型的判断,唯一的缺点是不支持自定义对象的判断。 测试代码如下: var str='str'; var arr=['1','2']; var num=1; var bool=true; var obj={name:'test'}; var nullObj=null; var undefinedObj=undefined; var reg=/reg/; function fn(){ alert('this is a function'); } function User(name){ this.name=name; } var user=new User('user'); var toString=Object.prototype.toString; console.log(toString.call(str)); console.log(toString.call(arr)); console.log(toString.call(num)); console.log(toString.call(bool)); console.log(toString.call(obj)); console.log(toString.call(reg)); console.log(toString.call(fn)); console.log(toString.call(user)); console.log(toString.call(nullObj)); console.log(toString.call(undefinedObj)); 运行结果: console.log(toString.call(user)); 的返回结果为:[object Object],不能做进一步判断。 总结 javascript中经常使用的对象判断方式包括:typeof、constructor和Object.prototype.toString.call()。其中typeof很好理解,他是JavaScript本身支持的语法。constructor很少使用,但是相信大家通过demo也能看懂他代表的意思。至于Object.prototype.toString.call()可能多少会让人有点费解,他和XXX.toString()有什么区别呢,为什么不能直接使用XXX.toString()呢? 我们在浏览器中运行下面的代码: var str='str'; var arr=['1','2']; var num=1; var bool=true; var obj={name:'test'}; var nullObj=null; var undefinedObj=undefined; var reg=/reg/; function fn(){ alert('this is a function'); } function User(name){ this.name=name; } var user=new User('user'); console.log(str.toString()); console.log(arr.toString()); console.log(num.toString()); console.log(bool.toString()); console.log(obj.toString()); console.log(reg.toString()); console.log(fn.toString()); console.log(user.toString()); console.log(nullObj.toString()); console.log(undefinedObj.toString()); 查看运行结果: null和undefined因为不存在toString()方法,所以会报错,我们就不去管他们了。至于其他对象,通过toString()返回的内容和使用Object.prototype.toString.call()返回的内容差别很大。这是因为Object.prototype.toString()方法被设计用来返回对象类型的。String、Array、Boolean、Regexp、Number和Function都继承自Object,同时也就继承了Object的原型方法toString(),但是他们都对toString()进行了重写。执行xxx.toString()时使用的是重写后的方法,返回的结果自然会和Object.prototype.toString.call()的结果不一致。 通过上面的例子,大家一定对这三种方式有了更深刻的认识,熟悉他们的优缺点,然后可以根据自己的需要选择合适的方式。推荐使用Object.prototype.toString.call()方法,因为他能解决绝大部分情况的判断,在遇到返回值为[object Object]时,再使用constructor辅助判断,看是否是自定义对象。
转载请注明原创地址:http://www.cnblogs.com/softlover/archive/2012/11/25/2787559.html 响应式web设计现在已经不是一个难事了,如果你还不熟悉他,可以参看我的文章《HTML5实践 -- 流式响应式设计》。如果你是一个初学者,可能响应式设计对你来说有点发杂,但实际上他比你想象的要简单的多。为了方便你更快的学习响应式设计,我特意写了这个教程,通过简单的三个步骤你就能掌握响应式设计的基本逻辑和media queries(假设你掌握css知识)。 demo预览地址:http://webdesignerwall.com/demo/responsive-design/index.html 步骤 1. Meta标签 大多数mobile浏览器,会将页面的宽度调整至viewport宽度,用以适应屏幕显示。这里我们会使用到viewport标签,例如下面的语句将添加在<head>之间,告诉浏览器使用设备的宽度作为viewport的宽度,取消initial scale的功能。 <meta name="viewport" content="width=device-width, initial-scale=1.0"> IE8和之前的浏览器不支持media query,我们需要使用 media-queries.js 或者 respond.js 实现ie浏览器对media query的支持。 <!--[if lt IE 9]> <script src="http://css3-mediaqueries-js.googlecode.com/svn/trunk/css3-mediaqueries.js"></script> <![endif]--> 步骤2. HTML结构 在这个例子中,我有一个基本的页面结构,包含header, content container, sidebar 和 footer。header的高度固定为180px,content container 的宽度为600px,sidebar 的宽度为300px。 步骤 3. Media Queries CSS3 media query 是响应式设计所使用的技巧,他就像是在写条件语句,告诉浏览器在特定viewport宽度下如何展示页面。 例如,下面的命令将会在viewport的宽度等于或者小于980px的时候起作用。一般而言,我会用百分比的数值,而不是像素值来设置容器的宽度,这样可以实现流式布局的效果。 当viewport等于或者小于700px的时候,设置 #content 和 #sidebar 的宽度为auto,并且移除 float, 这样他们会以全部宽度方式显示。 对于480px或者更小的mobile屏幕,设置#header的高度为自动,h1得字体大小为24px,同时隐藏#sidebar。 你可以根据需要添加很多media query,在我的demo中我只添加了三个media query。media query的目的针对特定viewport的宽度,使用不同的css实现页面布局。media query可以在一个css文件中,也可以分布在几个css文件中。 总结 本教程的目的是,向你展示响应式设计的基本要素,如果你想了解更多细节,可以参看我的文章《HTML5实践 -- 使用CSS3 Media Queries实现响应式设计》。 原文地址:http://webdesignerwall.com/tutorials/responsive-design-in-3-steps HTML5实践系列
转载请注明原文地址:http://www.cnblogs.com/softlover/archive/2012/11/25/2787558.html 当我编码Elemin Theme(我最近设计的一个响应式的站点)的时候,我遇到的一个跳帧就是,如何能让嵌入式的视频在尺寸变化上变得更加灵活。使用max-width:100% 和height:auto可以让html5的video标签很好的工作,但是这个解决方案不适用于iframe 或者 object标签的内嵌代码。通过几小时的寻找资料和实验,我最终找到了解决办法。当你在进行响应式设计的时候,这一css技巧能派上用场。你可以访问最终demo地址,缩放你的浏览器查看效果。 demo查看地址:http://webdesignerwall.com/demo/elastic-videos 灵活的html5 video标签(demo) 使用html5的video,可以通过设置max-width:100%让他变得灵活。前面的介绍中,已经提到他不适用于常用的iframe和object中的内嵌代码。 video { max-width: 100%; height: auto; } 灵活的 Object & Iframe 内嵌视频 这个技巧相当简单,你需要为video添加一个<div>容器,并且将div的padding-bottom属性值设置在50%到60%之间。然后设置子元素(ifame或者object)的width和height为100%,并且使用绝对定位。这样会迫使内嵌对象自动扩充到最大。 CSS .video-container { position: relative; padding-bottom: 56.25%; padding-top: 30px; height: 0; overflow: hidden; } .video-container iframe, .video-container object, .video-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } HTML <div class="video-container"> <iframe src="http://player.vimeo.com/video/6284199?title=0&byline=0&portrait=0" width="800" height="450" frameborder="0"></iframe> </div> 在固定宽度下实现灵活性 如果限制了视频的宽度,那么我们需要一个额外的<div>容器包裹video,并为div设置固定宽度和max-width:100%。 CSS .video-wrapper { width: 600px; max-width: 100%; } HTML <div class="video-wrapper"> <div class="video-container"> <iframe src="http://player.vimeo.com/video/6284199?title=0&byline=0&portrait=0" width="800" height="450" frameborder="0"></iframe> </div> <!-- /video --> </div> <!-- /video-wrapper --> 兼容性 这个技巧支持所有的浏览器,包括:Chrome, Safari, Firefox, Internet Explorer, Opera, iPhone 和 iPad。 原文地址:http://webdesignerwall.com/tutorials/css-elastic-videos HTML5实践系列
转载请注明原创地址:http://www.cnblogs.com/softlover/archive/2012/11/20/2779900.html 大家都知道viewport标签对于响应式设计的意义,但是你们可能不清楚,他对于非响应式设计也有相当的作用。如果你的站点还是非响应式的,那么通过本文你将学会,如何使用viewport标签增强你站点在mobile设备上的显示效果。 Viewport标签的一般使用 Viewport meta标签一般用在响应式设计中,用来设计mobile设备viewport的宽度和initial-scale。 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 在非响应式设计中使用Viewport 大家都知道,iphone默认的viewport宽度是980px。但是你的设计可能不符合这个范围,有时候宽点,有时候窄点。下面两个例子将向你展示,在什么情况下可以使用viewport标签来增强在mobile设备上非响应式设计中的展示效果。 例子 在iphone查看 Themify 站点。 截屏左边的图片展示了,站点在没有使用viewport标签时的效果,我们可以看到页面抵到了屏幕的边缘。截屏右边的图片是我添加了viewport标签后的效果,我将viewport的宽度设置为1024,这时的页面和手机屏幕在左右都将保持一定的距离。 <meta name="viewport" content="width=1024"> 另外一个例子 如果你设计的太窄,也会出现问题。假设你的设计时非响应式的,容器宽度是700px,这时的效果就像截屏左侧的图片,将会在手机屏幕右侧产生一个很大的空隙。 我们可以通过简单的添加一个720px宽度的viewport,来修复这个问题。我们没有对你的设计进行改变,但是iphone会做出调整,来适应你的720px宽度。 <meta name="viewport" content="width=720"> 通常的错误 一个通常的错误是,人们会为非响应式设计设置 initial-scale=1 参数。这样页面将会以100%的比例展示,不会进行比例的调整。这样人们就不得不移动页面或者执行缩小的操作,来查看整个页面。最糟糕的情况是,人们把 user-scalable=no 或者 maximum-scale=1 结合 initial-scale=1一起使用。这会禁用站点的缩放的功能,用户将不可能通过这种方式查看到整个页面。所以你一定要记住,如果你的站点不是响应式设计的,那么就不要这么设置! <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no"> 资源链接 iOS Documentation - Viewport Tag Mozilla - Viewport Tag Opera - Viewport Tag 原文地址:http://webdesignerwall.com/tutorials/viewport-meta-tag-for-non-responsive-design HTML5实践系列
转载请注明原创地址:http://www.cnblogs.com/softlover/archive/2012/11/21/2781388.html 现在屏幕分辨率的范围很大,从 320px (iPhone) 到 2560px (大型显示器),甚至更大。用户也不只是使用台式电脑访问web站点了,他使用手机、笔记本电脑、平板电脑。所以传统的设置网站宽度为固定值,已经不能满足需要了。web设计需要适应这种新要求,页面布局需要能够根据访问设备的不同分辨率自动进行调整。本教程将会向你介绍,如何使用html5和CSS3 Media Queries完成跨浏览器的响应式设计。 demo预览地址:http://webdesignerwall.com/demo/adaptive-design/final.html demo下载地址:http://www.webdesignerwall.com/file/adaptive-design-demo.zip 第一次运行 在开始之前,我们可以查看 最终demo 来查看最终效果。调整你浏览器的大小,我们可以看到页面会根据视窗的大小自动调整布局。 更多例子 你可以访问下面的地址,查看更多相关例子: WordPress themes。我设计了如下media queries: Tisa, Elemin, Suco, iTheme2, Funki, Minblr, 和 Wumblr。 概述 默认情况下,页面容器的宽度是980px,这个尺寸优化了大于1024px的分辨率。Media query用来检查 viewport 宽度,如果小于980px他会变为窄屏显示模式,页面布局将会以流动的宽度代替固定宽度。如果 viewport 小于650px,他会变为mobile显示模式,内容、侧边栏等内容会变为单独列布局方式,他们的宽度占满屏幕宽度。 HTML代码 在这里,我不会介绍下面html代码中的细节。下面是布局页面的主框架,我们有一个“pagewrap”的容器包装了"header", "content", "sidebar", 和 "footer"。 <div id="pagewrap"> <header id="header"> <hgroup> <h1 id="site-logo">Demo</h1> <h2 id="site-description">Site Description</h2> </hgroup> <nav> <ul id="main-nav"> <li><a href="#">Home</a></li> </ul> </nav> <form id="searchform"> <input type="search"> </form> </header> <div id="content"> <article class="post"> blog post </article> </div> <aside id="sidebar"> <section class="widget"> widget </section> </aside> <footer id="footer"> footer </footer> </div> HTML5.js 请注意,我在demo中使用了html5标签,不过IE9之前IE浏览器不支持<header>, <article>, <footer>, <figure>等html5新标签。可以在html文档中添加 html5.js 文件将解决这一问题。 <!--[if lt IE 9]> <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> CSS 设置html5元素为块状元素 下面的css将会把html5的元素(article, aside, figure, header, footer 等)设置为块元素。 article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } css主体结构 在这里我也不会解释css文件的细节。页面主容器“pagewrap”的宽度被设置为980px。header被设置为固定高度160px。“content”的宽度为600px,靠左浮动。“sidebar”宽度设置为280px,靠右浮动。 #pagewrap { width: 980px; margin: 0 auto; } #header { height: 160px; } #content { width: 600px; float: left; } #sidebar { width: 280px; float: right; } #footer { clear: both; } Step 1 Demo 我们可以通过demo查看当前效果。这时我们还没有使用 media queries,调整浏览器宽度,页面布局也不会发生变化。 CSS3 Media Query 你可以通过《HTML5实践 -- CSS3 Media Queries》了解更多信息。 包含 Media Queries Javascript文件 IE8和之前的浏览器不支持CSS3 media queries,你可以在页面中添加css3-mediaqueries.js来解决这个问题。 <!--[if lt IE 9]> <script src="http://css3-mediaqueries-js.googlecode.com/svn/trunk/css3-mediaqueries.js"></script> <![endif]--> 包含 Media Queries CSS 创建media query所需的css,然后在页面中添加引用。 <link href="media-queries.css" rel="stylesheet" type="text/css"> Viewport小于 980px(流动布局) 当viewport小于980px的时候,将会采用下面的规则: pagewrap = 宽度设置为 95% content = 宽度设置为 60% sidebar = 宽度设置为 30% tips:使用百分比(%)可以使容器变为不固定的。 @media screen and (max-width: 980px) { #pagewrap { width: 95%; } #content { width: 60%; padding: 3% 4%; } #sidebar { width: 30%; } #sidebar .widget { padding: 8% 7%; margin-bottom: 10px; } } Viewport小于 650px(单列布局) 当viewport小于650px的时候,将会采用下面的规则: header = 设置高度为 auto searchform = 重新设置 searchform 的位置 5px top main-nav = 设置位置为 static site-logo = 设置位置为 static site-description = 设置位置为 static content = 设置宽度为 auto (这会使容器宽度变为fullwidth) ,并且摆脱浮动 sidebar = 设置宽度为 100%,并且摆脱浮动 @media screen and (max-width: 650px) { #header { height: auto; } #searchform { position: absolute; top: 5px; right: 0; } #main-nav { position: static; } #site-logo { margin: 15px 100px 5px 0; position: static; } #site-description { margin: 0 0 15px; position: static; } #content { width: auto; float: none; margin: 20px 0; } #sidebar { width: 100%; float: none; margin: 0; } } Viewport小于 480px 下面得css是为了应对小于480px屏幕的情况,iphone横屏的时候就是这个宽度。 html = 禁用文字大小调整。 默认情况,iphone增大了字体大小,这样更便于阅读。你可以使用 -webkit-text-size-adjust: none; 来取消这种设置。 main-nav = 字体大小设置为 90% @media screen and (max-width: 480px) { html { -webkit-text-size-adjust: none; } #main-nav a { font-size: 90%; padding: 10px 8px; } } 弹性的图片 为了让图片尺寸变得更为弹性,可以简单的添加 max-width:100% 和 height:auto。这种方式在IE7中正常工作,不能在IE8中工作,需要使用 width:auto\9 来解决这个问题。 img { max-width: 100%; height: auto; width: auto\9; /* ie8 */ } 弹性的嵌入视频 为了使嵌入视频也变得更加弹性,也可以使用上面的方法。但是不知道什么原因,max-width:100% 在safari浏览器中不能正常的在嵌入资源中工作。我们需要使用width:100% 来代替他。 .video embed, .video object, .video iframe { width: 100%; height: auto; } initial-scale Meta 标签 (iPhone) 默认情况下,iphone的safari浏览器会收缩页面,以适应他的屏幕。下面的语句告诉iphone的safari浏览器,使用设备宽度作为viewport的宽度,并且禁用initial-scale。 <meta name="viewport" content="width=device-width; initial-scale=1.0"> 最终效果 查看最终的demo,调整浏览器的大小,查看media query 的行为。你也可以使用iPhone, iPad, 新版Blackberry, 和 Android 来查看modile版的效果。 总结 可靠的Media Queries Javascript 可以使用css3-mediaqueries.js来解决浏览器不支持media queries的问题。 <!--[if lt IE 9]> <script src="http://css3-mediaqueries-js.googlecode.com/svn/trunk/css3-mediaqueries.js"></script> <![endif]--> CSS Media Queries 这一技巧可以创建自适应的设计,可以根据 viewport 的宽度重写布局的css。 @media screen and (max-width: 560px) { #content { width: auto; float: none; } #sidebar { width: 100%; float: none; } } 弹性的图片 使用max-width:100% 和 height:auto,可以让图片变得更加弹性。 img { max-width: 100%; height: auto; width: auto\9; /* ie8 */ } 弹性的内嵌视频 使用width:100% 和 height:auto,可以让内嵌视频变得更加弹性。 .video embed, .video object, .video iframe { width: 100%; height: auto; } Webkit字体大小调整 使用-webkit-text-size-adjust:none,在iphone上禁用字体大小调整。 html { -webkit-text-size-adjust: none; } 设置iPhone Viewport 和 Initial Scale 下面的语句实现了在iphone中,使用meta标签设置viewport 和 inital scale。 <meta name="viewport" content="width=device-width; initial-scale=1.0"> 好了,今天的教程到此为止。 原文地址:http://webdesignerwall.com/tutorials/responsive-design-with-css3-media-queries HTML5实践系列
转载请注明原创地址:http://www.cnblogs.com/softlover/archive/2012/11/25/2787429.html demo查看地址:http://www.webdesignerwall.com/demo/media-queries/ CSS2允许你对特定media类型制定样式,例如针对屏幕或者打印机。css3提供了更加强大的media queries,你可以针对不同media类型设置表达式,根据不同的条件设置不同的样式。例如你可以为大屏幕设置一种样式,为mobile设置另外一种样式。这个功能相当强大,你可以不修改页面内容的情况下,为不同设备提供不同的样式效果。下面的课程我们将会介绍到一些使用该技术的站点。 CSS3 Media Queries 打开我的demo页面,调整浏览器打大小,查看页面布局变化情况。 Max Width 当页面视图区域小于600px宽度的时候,css会被使用到。 @media screen and (max-width: 600px) { .class { background: #ccc; } } 你也可以使用下面的方式,在页面的<head>中引用外部css文件。 <link rel="stylesheet" media="screen and (max-width: 600px)" href="small.css" /> Min Width 当视图区域大于900px宽度的时候,css会被使用到。 @media screen and (min-width: 900px) { .class { background: #666; } } 多个 Media Queries 你可以把多个media queries组合在一起,当视图区域宽度在600px到900px之间的时候,会使用下面的css。 @media screen and (min-width: 600px) and (max-width: 900px) { .class { background: #333; } } 设备Width 下面的css会在 max-device-width为480px的时候使用,例如iphone。 note:max-device-width指的是设备实际分辨率,max-width指的是可是区域尺寸。 @media screen and (max-device-width: 480px) { .class { background: #000; } } 针对 iPhone 4 下面的时针对iphone4的css。 <link rel="stylesheet" media="only screen and (-webkit-min-device-pixel-ratio: 2)" type="text/css" href="iphone4.css" /> 针对 iPad 你也可以在ipad上检查定位(portrait 或者 landscapse)。 <link rel="stylesheet" media="all and (orientation:portrait)" href="portrait.css"> <link rel="stylesheet" media="all and (orientation:landscape)" href="landscape.css"> 针对Internet Explorer的Media Queries 因为ie8以及之前版本的ie浏览器不支持media query,你需要使用JavaScript的hack计较解决问题。下面是一些解决方案: CSS Tricks - 使用jquery判断浏览器尺寸 The Man in Blue - 使用Javascript (这篇文章是六年前写的) jQuery Media Queries 插件 示例站点 你可以使用支持media query的浏览器访问下面的站点,例如:Firefox, Chrome, 和 Safari。可以查看他们针对浏览器宽度所做的布局响应。 Hicksdesign 大尺寸: 3 列sidebar 小尺寸: 2 列sidebar (中间的sidebar跑到了左边) 更小尺寸: 1 列sidebar (最右边的跑到了logo下面) 最小尺寸: 没有sidebar (logo 和 右侧的sidebar 上移,其他sidebar 下移) Colly 页面布局根据浏览器的可视区域,在1列、2列和4列之间切换。 A List Apart 大尺寸:导航在上不部, 1行图片 中等尺寸:导航在左边, 3列图片 小尺寸:导航在上部,logo没有背景图片, 3列图片 Tee Gallery 他和之前的Colly有点像,不同点在于它的图片会根据页面布局的变化,进行缩放。这里使用的技巧就是,对图片使用百分比宽度,代替固定宽度,例如:width=100%。 总结 我们需要注意到,针对mobile做了一个css,并不意味着我们的站点对mobile设备就是优化的。对mobile设备进行优化,网站图片和html代码同样需要缩小尺寸,这样才有益于加载。media query做到的只是设计展现,而不是优化操作。 原文地址:http://webdesignerwall.com/tutorials/css3-media-queries HTML5实践系列
原文地址:http://dudo.org/archives/2009060611295.html Google近日推出了一款网站性能优化工具:Page Speed(http://code.google.com/speed/page-speed/)。它旨在帮助站长与网站开发者分析网站中存在的性能方面的问题,并有针对性地提出改进意见。Page Speed在功能方面极其类似于Yahoo!的网站性能优化YSlow,不过YSlow要比Page Speed推出早得的多。它们都是基于Firebug的Fireffox插件,使用方法也类似。这里我主要介绍一下Google新推出的Page Speed的使用,对Yslow感兴趣的朋友可以参照我以前的这篇文章《你的网站为什么会慢?——用YSlow为你的网站提速》,同时还有我翻译的Yahoo!的文章Yahoo!网站性能最佳体验的34条黄金守则——内容、JavaScript和CSS、服务器、图片、Coockie与移动应用,相信一定会对你提高网站性能有帮助。 一、Page Speed的安装及使用 Page Speed是一款Firefox插件,同时他依附于别款插件Firebug,也就是说你的Firefox浏览器中必须已经安装了Firebug才能安装Page Speed。安装环境为Firefox 3.0.4以上,Fireug 1.3.3以上。 Page Speed的使用也很简单,在Firefox中点击右下角的Firebug图标启动后,再点击Page Speed选项卡即可。要注意的是,你要对你网站内的某个页面进行性能分析,你必须先把该页面加载完成后才能使用Page Speed,也就是说只有在浏览器左下角出现“Done”或者"完成"之后才可以启用Page Speed进行分析。如果页面中流媒体,可能不会现在“完成”,这种情况要等到流媒体可以播放。 然后点击“Analyze Performance”(性能分析),这时Page Speed会根据web performance best practices (网页性能最佳实践)进行逐项打分。然后根据重要程序和优先级对每项进行排列。 此外,你还可以点击每条建议前面的“加号”展开查看详细的描述,或者直接点击每条规则相看该规则的具体内容,还可以点击“Show Resource”(查看来源)来查看每条建议是针对页面中哪部分内容提出的。 对于分析结果中的符号说明一下: 红色感叹号代表高优先级提示,表示这一项严重影响了你的页面性能,你需要优先对其进行性能优化; 橙色三角代表此项提示需要引起你的注意,并进行适当改进; 绿色的对号代表该项规则在你的网站中应用得到,你在修改了前面两部分的提示之后,它们有可能变为绿色的对号; 蓝色消息符号是为你提供了额外的帮助信息,请稍加留意(需要注意的是,如果你的页面中出现了大量的此类符号,可能是因为你在页面加载完成之前就进行了网站性能分析)。 二、活动记录 活动记录是一条页面活动的时间轴,它记录了包括网络事件、JavaScript运行在内的所有浏览器活动。你可以使用它并配合性能分析中的数据进一步对网站性能做出评估。 查看页面运行过程中所耗费的时间,以毫秒计算; 查看浏览器事件,包括页面加载完成后的事件; 区分造成页面响应缓慢的原因,其中包括网络来时、DNS查找、连接建立、JavaScript运行等; 获取在特定时间或者事件下才响应的JavaScript事件列表; 可以对其它标签或者窗口中打开的页面进行分析; 多页面加载时的页面加载顺序; 对根据Page Speed优化前后的表现进行对比。 三、理解Page Speed中的事件 页面记录选项卡下是通过时间线来记录各种资源加载到页面所有需要的时间。事件的记录时间间隔为10毫秒,如果事件需要的时间少于10毫秒那么它将用较短的色块来表示。时间线中没有任何颜色的表示,在浏览器事件的运行依赖于其它进程,如DOM和CSS渲染、Flash ActionScript、渲染、操作系统事件等。 网络事件 描述 DNS 浏览器查找DNS所需要的时间 t连接等待 浏览器与网站服务器建立连接(TCP)需要一定的时间。由于浏览器可以打开的连接数目是有限的,如果达到这个限制他必须等其它连接关闭之后才能再重新建立一个新的连接。(更多关于浏览器连接的信息可以参照Parallel downloads across hostnames)。 这个事件显示了浏览器等其它连接完成的时间。 连接 浏览器和web服务器建立连接。这个事件只有打开新连接时出现,已有连接重新打开使用不包含在内。 请求发送 浏览器发送的HTTP请求。只显示GET方式的请求。 已连接 浏览器通过网络等待接收数据。事件随着浏览器TCP连接的结束而结束。 本地事件 描述 缓存 浏览器成功将内容加入到缓存中。 可用数据 可用于浏览器呈现的数据。由于web服务器发送大量的数据,如果文件很大那么有可能一个资源会出现多个该事件。 获取JS 浏览器获取JavaScript。该事件可能会延缓其它事件,如果此种情况出现,将会在其下一行列出。 运行JS 浏览器执行JavaScript。该事件可能会延缓其它事件,如果此种情况出现,将会在其下一行列出。如果获取JS和运行JS中间有时间间隔,这说明源文件中包括有延时功能的函数。 此外,Page Speed还包括了对已完成的JavaScript函数的信息搜集功能,当页面中的JS函数一旦运行,PageSpeed就会捕捉到相关信息。不通过对Page Speed进行设置还可以对未触发函数、延时加载函数等进行收集。 下面的图片显示了7800毫秒时已经加载但还未触发的函数列表: 而下面则显示是已经触发运行了的JS函数: 此外Pge Speed还有诸如JavaScript函数控制、浏览器User Agent设置等更高级功能。具体使用大家可以与YSlow对比一下。 相信,用好这两款工具,对于站长和网站开发者来说会有极大的帮助。
原文地址:http://dudo.org/archives/2008051511220.html 英文地址:http://developer.yahoo.com/performance/rules.html中文地址:http://www.dudo.org/article.asp?id=218 我们在前面的几节中分别讲了提高网站性能中内容、服务器、JavaScript和CSS等方面的内容。除此之外,图片和Coockie也是我们网站中几乎不可缺少组成部分,此外随着移动设备的流行,对于移动应用的优化也十分重要。这主要包括:Coockie: 减小Cookie体积 对于页面内容使用无coockie域名 图片: 优化图像 优化CSS Spirite 不要在HTML中缩放图像 favicon.ico要小而且可缓存 移动应用: 保持单个内容小于25K 打包组件成复合文本 27、减小Cookie体积 HTTP coockie可以用于权限验证和个性化身份等多种用途。coockie内的有关信息是通过HTTP文件头来在web服务器和浏览器之间进行交流的。因此保持coockie尽可能的小以减少用户的响应时间十分重要。有关更多信息可以查看Tenni Theurer和Patty Chi的文章“When the Cookie Crumbles”。这们研究中主要包括: 去除不必要的coockie 使coockie体积尽量小以减少对用户响应的影响 注意在适应级别的域名上设置coockie以便使子域名不受影响 设置合理的过期时间。较早地Expire时间和不要过早去清除coockie,都会改善用户的响应时间。 28、对于页面内容使用无coockie域名 当浏览器在请求中同时请求一张静态的图片和发送coockie时,服务器对于这些coockie不会做任何地使用。因此他们只是因为某些负面因素而创建的网络传输。所有你应该确定对于静态内容的请求是无coockie的请求。创建一个子域名并用他来存放所有静态内容。 如果你的域名是www.example.org,你可以在static.example.org上存在静态内容。但是,如果你不是在www.example.org上而是在顶级域名example.org设置了coockie,那么所有对于static.example.org的请求都包含coockie。在这种情况下,你可以再重新购买一个新的域名来存在静态内容,并且要保持这个域名是无coockie的。Yahoo!使用的是ymig.com,YouTube使用的是ytimg.com,Amazon使用的是images-anazon.com等等。 使用无coockie域名存在静态内容的另外一个好处就是一些代理(服务器)可能会拒绝对coockie的内容请求进行缓存。一个相关的建议就是,如果你想确定应该使用example.org还是www.example.org作为你的一主页,你要考虑到coockie带来的影响。忽略掉www会使你除了把coockie设置到*.example.org(*是泛域名解析,代表了所有子域名)外没有其它选择,因此出于性能方面的考虑最好是使用带有www的子域名并且在它上面设置coockie。 29、优化图像 设计人员完成对页面的设计之后,不要急于将它们上传到web服务器,这里还需要做几件事: 你可以检查一下你的GIF图片中图像颜色的数量是否和调色板规格一致。 使用imagemagick中下面的命令行很容易检查:identify -verbose image.gif 如果你发现图片中只用到了4种颜色,而在调色板的中显示的256色的颜色槽,那么这张图片就还有压缩的空间。 尝试把GIF格式转换成PNG格式,看看是否节省空间。大多数情况下是可以压缩的。由于浏览器支持有限,设计者们往往不太乐意使用PNG格式的图片,不过这都是过去的事情了。现在只有一个问题就是在真彩PNG格式中的alpha通道半透明问题,不过同样的,GIF也不是真彩格式也不支持半透明。因此GIF能做到的,PNG(PNG8)同样也能做到(除了动画)。下面这条简单的命令可以安全地把GIF格式转换为PNG格式:convert image.gif image.png“我们要说的是:给PNG一个施展身手的机会吧!” 在所有的PNG图片上运行pngcrush(或者其它PNG优化工具)。例如:pngcrush image.png -rem alla -reduce -brute result.png 在所有的JPEG图片上运行jpegtran。这个工具可以对图片中的出现的锯齿等做无损操作,同时它还可以用于优化和清除图片中的注释以及其它无用信息(如EXIF信息):jpegtran -copy none -optimize -perfect src.jpg dest.jpg 30、优化CSS Spirite 在Spirite中水平排列你的图片,垂直排列会稍稍增加文件大小; Spirite中把颜色较近的组合在一起可以降低颜色数,理想状况是低于256色以便适用PNG8格式; 便于移动,不要在Spirite的图像中间留有较大空隙。这虽然不大会增加文件大小但对于用户代理来说它需要更少的内存来把图片解压为像素地图。100×100的图片为1万像素,而1000×1000就是100万像素。 31、不要在HTML中缩放图像 不要为了在HTML中设置长宽而使用比实际需要大的图片。如果你需要:<img width="100" height="100" src="mycat.jpg" alt="My Cat" />那么你的图片(mycat.jpg)就应该是100×100像素而不是把一个500×500像素的图片缩小使用。 32、favicon.ico要小而且可缓存 favicon.ico是位于服务器根目录下的一个图片文件。它是必定存在的,因为即使你不关心它是否有用,浏览器也会对它发出请求,因此最好不要返回一个404 Not Found的响应。由于是在同一台服务器上,它每被请求一次coockie就会被发送一次。这个图片文件还会影响下载顺序,例如在IE中当你在onload中请求额外的文件时,favicon会在这些额外内容被加载前下载。 因此,为了减少favicon.ico带来的弊端,要做到: 文件尽量地小,最好小于1K 在适当的时候(也就是你不要打算再换favicon.ico的时候,因为更换新文件时不能对它进行重命名)为它设置Expires文件头。你可以很安全地把Expires文件头设置为未来的几个月。你可以通过核对当前favicon.ico的上次编辑时间来作出判断。 Imagemagick可以帮你创建小巧的favicon。 33、保持单个内容小于25K 这条限制主要是因为iPhone不能缓存大于25K的文件。注意这里指的是解压缩后的大小。由于单纯gizp压缩可能达不要求,因此精简文件就显得十分重要。 查看更多信息,请参阅Wayne Shea和Tenni Theurer的文件“Performance Research, Part 5: iPhone Cacheability – Making it Stick”。 34、打包组件成复合文本 把页面内容打包成复合文本就如同带有多附件的Email,它能够使你在一个HTTP请求中取得多个组件(切记:HTTP请求是很奢侈的)。当你使用这条规则时,首先要确定用户代理是否支持(iPhone就不支持)。
原文地址:http://dudo.org/archives/2008051417218.html 英文地址:http://developer.yahoo.com/performance/rules.html中文地址:http://www.dudo.org/article.asp?id=216 在第一部分和第二部分中我们分别介绍了改善网站性能中页面内容和服务器的几条守则,除此之外,JavaScript和CSS也是我们页面中经常用到的内容,对它们的优化也提高网站性能的重要方面:CSS: 把样式表置于顶部 避免使用CSS表达式(Expression) 使用外部JavaScript和CSS 削减JavaScript和CSS 用<link>代替@import 避免使用滤镜 JavaScript 把脚本置于页面底部 使用外部JavaScript和CSS 削减JavaScript和CSS 剔除重复脚本 减少DOM访问 开发智能事件处理程序 17、把样式表置于顶部 在研究Yahoo!的性能表现时,我们发现把样式表放到文档的<head />内部似乎会加快页面的下载速度。这是因为把样式表放到<head />内会使页面有步骤的加载显示。 注重性能的前端服务器往往希望页面有秩序地加载。同时,我们也希望浏览器把已经接收到内容尽可能显示出来。这对于拥有较多内容的页面和网速较慢的用户来说特别重要。向用户返回可视化的反馈,比如进程指针,已经有了较好的研究并形成了正式文档。在我们的研究中HTML页面就是进程指针。当浏览器有序地加载文件头、导航栏、顶部的logo等对于等待页面加载的用户来说都可以作为可视化的反馈。这从整体上改善了用户体验。 把样式表放在文档底部的问题是在包括Internet Explorer在内的很多浏览器中这会中止内容的有序呈现。浏览器中止呈现是为了避免样式改变引起的页面元素重绘。用户不得不面对一个空白页面。 HTML规范清楚指出样式表要放包含在页面的<head />区域内:“和<a />不同,<link />只能出现在文档的<head />区域内,尽管它可以多次使用它”。无论是引起白屏还是出现没有样式化的内容都不值得去尝试。最好的方案就是按照HTML规范在文档<head />内加载你的样式表。 18、避免使用CSS表达式(Expression) CSS表达式是动态设置CSS属性的强大(但危险)方法。Internet Explorer从第5个版本开始支持CSS表达式。下面的例子中,使用CSS表达式可以实现隔一个小时切换一次背景颜色: background-color: expression( (new Date()).getHours()%2 ? "#B8D4FF" : "#F08A00" ); 如上所示,expression中使用了JavaScript表达式。CSS属性根据JavaScript表达式的计算结果来设置。expression方法在其它浏览器中不起作用,因此在跨浏览器的设计中单独针对Internet Explorer设置时会比较有用。 表达式的问题就在于它的计算频率要比我们想象的多。不仅仅是在页面显示和缩放时,就是在页面滚动、乃至移动鼠标时都会要重新计算一次。给CSS表达式增加一个计数器可以跟踪表达式的计算频率。在页面中随便移动鼠标都可以轻松达到10000次以上的计算量。 一个减少CSS表达式计算次数的方法就是使用一次性的表达式,它在第一次运行时将结果赋给指定的样式属性,并用这个属性来代替CSS表达式。如果样式属性必须在页面周期内动态地改变,使用事件句柄来代替CSS表达式是一个可行办法。如果必须使用CSS表达式,一定要记住它们要计算成千上万次并且可能会对你页面的性能产生影响。 19、使用外部JavaScript和CSS 很多性能规则都是关于如何处理外部文件的。但是,在你采取这些措施前你可能会问到一个更基本的问题:JavaScript和CSS是应该放在外部文件中呢还是把它们放在页面本身之内呢? 在实际应用中使用外部文件可以提高页面速度,因为JavaScript和CSS文件都能在浏览器中产生缓存。内置在HTML文档中的JavaScript和CSS则会在每次请求中随HTML文档重新下载。这虽然减少了HTTP请求的次数,却增加了HTML文档的大小。从另一方面来说,如果外部文件中的JavaScript和CSS被浏览器缓存,在没有增加HTTP请求次数的同时可以减少HTML文档的大小。 关键问题是,外部JavaScript和CSS文件缓存的频率和请求HTML文档的次数有关。虽然有一定的难度,但是仍然有一些指标可以一测量它。如果一个会话中用户会浏览你网站中的多个页面,并且这些页面中会重复使用相同的脚本和样式表,缓存外部文件就会带来更大的益处。 许多网站没有功能建立这些指标。对于这些网站来说,最好的坚决方法就是把JavaScript和CSS作为外部文件引用。比较适合使用内置代码的例外就是网站的主页,如Yahoo!主页和My Yahoo!。主页在一次会话中拥有较少(可能只有一次)的浏览量,你可以发现内置JavaScript和CSS对于终端用户来说会加快响应时 间。 对于拥有较大浏览量的首页来说,有一种技术可以平衡内置代码带来的HTTP请求减少与通过使用外部文件进行缓存带来的好处。其中一个就是在首页中内置JavaScript和CSS,但是在页面下载完成后动态下载外部文件,在子页面中使用到这些文件时,它们已经缓存到浏览器了。 20、削减JavaScript和CSS 精简是指从去除代码不必要的字符减少文件大小从而节省下载时间。消减代码时,所有的注释、不需要的空白字符(空格、换行、tab缩进)等都要去掉。在JavaScript中,由于需要下载的文件体积变小了从而节省了响应时间。精简JavaScript中目前用到的最广泛的两个工具是JSMin和YUI Compressor。YUI Compressor还可用于精简CSS。 混淆是另外一种可用于源代码优化的方法。这种方法要比精简复杂一些并且在混淆的过程更易产生问题。在对美国前10大网站的调查中发现,精简也可以缩小原来代码体积的21%,而混淆可以达到25%。尽管混淆法可以更好地缩减代码,但是对于JavaScript来说精简的风险更小。 除消减外部的脚本和样式表文件外,<script>和<style>代码块也可以并且应该进行消减。即使你用Gzip压缩过脚本和样式表,精简这些文件仍然可以节省5%以上的空间。由于JavaScript和CSS的功能和体积的增加,消减代码将会获得益处。 21、用<link>代替@import 前面的最佳实现中提到CSS应该放置在顶端以利于有序加载呈现。 在IE中,页面底部@import和使用<link>作用是一样的,因此最好不要使用它。 22、避免使用滤镜 IE独有属性AlphaImageLoader用于修正7.0以下版本中显示PNG图片的半透明效果。这个滤镜的问题在于浏览器加载图片时它会终止内容的呈现并且冻结浏览器。在每一个元素(不仅仅是图片)它都会运算一次,增加了内存开支,因此它的问题是多方面的。 完全避免使用AlphaImageLoader的最好方法就是使用PNG8格式来代替,这种格式能在IE中很好地工作。如果你确实需要使用AlphaImageLoader,请使用下划线_filter又使之对IE7以上版本的用户无效。 23、把脚本置于页面底部 脚本带来的问题就是它阻止了页面的平行下载。HTTP/1.1 规范建议,浏览器每个主机名的并行下载内容不超过两个。如果你的图片放在多个主机名上,你可以在每个并行下载中同时下载2个以上的文件。但是当下载脚本时,浏览器就不会同时下载其它文件了,即便是主机名不相同。 在某些情况下把脚本移到页面底部可能不太容易。比如说,如果脚本中使用了document.write来插入页面内容,它就不能被往下移动了。这里可能还会有作用域的问题。很多情况下,都会遇到这方面的问题。 一个经常用到的替代方法就是使用延迟脚本。DEFER属性表明脚本中没有包含document.write,它告诉浏览器继续显示。不幸的是,Firefox并不支持DEFER属性。在Internet Explorer中,脚本可能会被延迟但效果也不会像我们所期望的那样。如果脚本可以被延迟,那么它就可以移到页面的底部。这会让你的页面加载的快一点。 24、剔除重复脚本 在同一个页面中重复引用JavaScript文件会影响页面的性能。你可能会认为这种情况并不多见。对于美国前10大网站的调查显示其中有两家存在重复引用脚本的情况。有两种主要因素导致一个脚本被重复引用的奇怪现象发生:团队规模和脚本数量。如果真的存在这种情况,重复脚本会引起不必要的HTTP请求和无用的JavaScript运算,这降低了网站性能。 在Internet Explorer中会产生不必要的HTTP请求,而在Firefox却不会。在Internet Explorer中,如果一个脚本被引用两次而且它又不可缓存,它就会在页面加载过程中产生两次HTTP请求。即时脚本可以缓存,当用户重载页面时也会产生额外的HTTP请求。 除增加额外的HTTP请求外,多次运算脚本也会浪费时间。在Internet Explorer和Firefox中不管脚本是否可缓存,它们都存在重复运算JavaScript的问题。 一个避免偶尔发生的两次引用同一脚本的方法是在模板中使用脚本管理模块引用脚本。在HTML页面中使用<script />标签引用脚本的最常见方法就是: <script type="text/javascript" src="menu_1.0.17.js"></script> 在PHP中可以通过创建名为insertScript的方法来替代: <?php insertScript("menu.js") ?> 为了防止多次重复引用脚本,这个方法中还应该使用其它机制来处理脚本,如检查所属目录和为脚本文件名中增加版本号以用于Expire文件头等。 25、减少DOM访问 使用JavaScript访问DOM元素比较慢,因此为了获得更多的应该页面,应该做到: 缓存已经访问过的有关元素 线下更新完节点之后再将它们添加到文档树中 避免使用JavaScript来修改页面布局 有关此方面的更多信息请查看Julien Lecomte在YUI专题中的文章“高性能Ajax应该程序”。 26、开发智能事件处理程序 有时候我们会感觉到页面反应迟钝,这是因为DOM树元素中附加了过多的事件句柄并且些事件句病被频繁地触发。这就是为什么说使用event delegation(事件代理)是一种好方法了。如果你在一个div中有10个按钮,你只需要在div上附加一次事件句柄就可以了,而不用去为每一个按钮增加一个句柄。事件冒泡时你可以捕捉到事件并判断出是哪个事件发出的。 你同样也不用为了操作DOM树而等待onload事件的发生。你需要做的就是等待树结构中你要访问的元素出现。你也不用等待所有图像都加载完毕。 你可能会希望用DOMContentLoaded事件来代替onload,但是在所有浏览器都支持它之前你可使用YUI 事件应用程序中的onAvailable方法。 有关此方面的更多信息请查看Julien Lecomte在YUI专题中的文章“高性能Ajax应该程序”。
原文地址:http://dudo.org/archives/2008051322217.html 英文地址:http://developer.yahoo.com/performance/rules.html中文地址:http://www.dudo.org/article.asp?id=215 在本系列的第一节中,讲了提高网站性能中网站“内容”有关的10条原则。除了在网站在内容上的改进外,在网站服务器端上也有需要注意和改进的地方,它们包括: 使用内容分发网络 为文件头指定Expires或Cache-Control Gzip压缩文件内容 配置ETag 尽早刷新输出缓冲 使用GET来完成AJAX请求 11、使用内容分发网络 用户与你网站服务器的接近程度会影响响应时间的长短。把你的网站内容分散到多个、处于不同地域位置的服务器上可以加快下载速度。但是首先我们应该做些什么呢? 按地域布置网站内容的第一步并不是要尝试重新架构你的网站让他们在分发服务器上正常运行。根据应用的需求来改变网站结构,这可能会包括一些比较复杂的任务,如在服务器间同步Session状态和合并数据库更新等。要想缩短用户和内容服务器的距离,这些架构步骤可能是不可避免的。 要记住,在终端用户的响应时间中有80%到90%的响应时间用于下载图像、样式表、脚本、Flash等页面内容。这就是网站性能黄金守则。和重新设计你的应用程序架构这样比较困难的任务相比,首先来分布静态内容会更好一点。这不仅会缩短响应时间,而且对于内容分发网络来说它更容易实现。 内容分发网络(Content Delivery Network,CDN)是由一系列分散到各个不同地理位置上的Web服务器组成的,它提高了网站内容的传输速度。用于向用户传输内容的服务器主要是根据和用户在网络上的靠近程度来指定的。例如,拥有最少网络跳数(network hops)和响应速度最快的服务器会被选定。 一些大型的网络公司拥有自己的CDN,但是使用像Akamai Technologies,Mirror Image Internet, 或者Limelight Networks这样的CDN服务成本却非常高。对于刚刚起步的企业和个人网站来说,可能没有使用CDN的成本预算,但是随着目标用户群的不断扩大和更加全球化,CDN就是实现快速响应所必需的了。以Yahoo来说,他们转移到CDN上的网站程序静态内容节省了终端用户20%以上的响应时间。使用CDN是一个只需要相对简单地修改代码实现显著改善网站访问速度的方法。 12、为文件头指定Expires或Cache-Control 这条守则包括两方面的内容:对于静态内容:设置文件头过期时间Expires的值为“Never expire”(永不过期)对于动态内容:使用恰当的Cache-Control文件头来帮助浏览器进行有条件的请求 网页内容设计现在越来越丰富,这就意味着页面中要包含更多的脚本、样式表、图片和Flash。第一次访问你页面的用户就意味着进行多次的HTTP请求,但是通过使用Expires文件头就可以使这样内容具有缓存性。它避免了接下来的页面访问中不必要的HTTP请求。Expires文件头经常用于图像文件,但是应该在所有的内容都使用他,包括脚本、样式表和Flash等。 浏览器(和代理)使用缓存来减少HTTP请求的大小和次数以加快页面访问速度。Web服务器在HTTP响应中使用Expires文件头来告诉客户端内容需要缓存多长时间。下面这个例子是一个较长时间的Expires文件头,它告诉浏览器这个响应直到2010年4月15日才过期。 Expires: Thu, 15 Apr 2010 20:00:00 GMT 如果你使用的是Apache服务器,可以使用ExpiresDefault来设定相对当前日期的过期时间。下面这个例子是使用ExpiresDefault来设定请求时间后10年过期的文件头: ExpiresDefault "access plus 10 years" 要切记,如果使用了Expires文件头,当页面内容改变时就必须改变内容的文件名。依Yahoo!来说我们经常使用这样的步骤:在内容的文件名中加上版本号,如yahoo_2.0.6.js。 使用Expires文件头只有会在用户已经访问过你的网站后才会起作用。当用户首次访问你的网站时这对减少HTTP请求次数来说是无效的,因为浏览器的缓存是空的。因此这种方法对于你网站性能的改进情况要依据他们“预缓存”存在时对你页面的点击频率(“预缓存”中已经包含了页面中的所有内容)。Yahoo!建立了一套测量方法,我们发现所有的页面浏览量中有75~85%都有“预缓存”。通过使用Expires文件头,增加了缓存在浏览器中内容的数量,并且可以在用户接下来的请求中再次使用这些内容,这甚至都不需要通过用户发送一个字节的请求。 13、Gzip压缩文件内容 网络传输中的HTTP请求和应答时间可以通过前端机制得到显著改善。的确,终端用户的带宽、互联网提供者、与对等交换点的靠近程度等都不是网站开发者所能决定的。但是还有其他因素影响着响应时间。通过减小HTTP响应的大小可以节省HTTP响应时间。 从HTTP/1.1开始,web客户端都默认支持HTTP请求中有Accept-Encoding文件头的压缩格式: Accept-Encoding: gzip, deflate 如果web服务器在请求的文件头中检测到上面的代码,就会以客户端列出的方式压缩响应内容。Web服务器把压缩方式通过响应文件头中的Content-Encoding来返回给浏览器。 Content-Encoding: gzip Gzip是目前最流行也是最有效的压缩方式。这是由GNU项目开发并通过RFC 1952来标准化的。另外仅有的一个压缩格式是deflate,但是它的使用范围有限效果也稍稍逊色。 Gzip大概可以减少70%的响应规模。目前大约有90%通过浏览器传输的互联网交换支持gzip格式。如果你使用的是Apache,gzip模块配置和你的版本有关:Apache 1.3使用mod_zip,而Apache 2.x使用moflate。 浏览器和代理都会存在这样的问题:浏览器期望收到的和实际接收到的内容会存在不匹配的现象。幸好,这种特殊情况随着旧式浏览器使用量的减少在减少。Apache模块会通过自动添加适当的Vary响应文件头来避免这种状况的出现。 服务器根据文件类型来选择需要进行gzip压缩的文件,但是这过于限制了可压缩的文件。大多数web服务器会压缩HTML文档。对脚本和样式表进行压缩同样也是值得做的事情,但是很多web服务器都没有这个功能。实际上,压缩任何一个文本类型的响应,包括XML和JSON,都值得的。图像和PDF文件由于已经压缩过了所以不能再进行gzip压缩。如果试图gizp压缩这些文件的话不但会浪费CPU资源还会增加文件的大小。 Gzip压缩所有可能的文件类型是减少文件体积增加用户体验的简单方法。 14、配置ETag Entity tags(ETags)(实体标签)是web服务器和浏览器用于判断浏览器缓存中的内容和服务器中的原始内容是否匹配的一种机制(“实体”就是所说的“内容”,包括图片、脚本、样式表等)。增加ETag为实体的验证提供了一个比使用“last-modified date(上次编辑时间)”更加灵活的机制。Etag是一个识别内容版本号的唯一字符串。唯一的格式限制就是它必须包含在双引号内。原始服务器通过含有ETag文件头的响应指定页面内容的ETag。 HTTP/1.1 200 OK Last-Modified: Tue, 12 Dec 2006 03:03:59 GMT ETag: "10c24bc-4ab-457e1c1f" Content-Length: 12195 稍后,如果浏览器要验证一个文件,它会使用If-None-Match文件头来把ETag传回给原始服务器。在这个例子中,如果ETag匹配,就会返回一个304状态码,这就节省了12195字节的响应。 GET /i/yahoo.gif HTTP/1.1 Host: us.yimg.com If-Modified-Since: Tue, 12 Dec 2006 03:03:59 GMT If-None-Match: "10c24bc-4ab-457e1c1f" HTTP/1.1 304 Not Modified ETag的问题在于,它是根据可以辨别网站所在的服务器的具有唯一性的属性来生成的。当浏览器从一台服务器上获得页面内容后到另外一台服务器上进行验证时ETag就会不匹配,这种情况对于使用服务器组和处理请求的网站来说是非常常见的。默认情况下,Apache和IIS都会把数据嵌入ETag中,这会显著减少多服务器间的文件验证冲突。 Apache 1.3和2.x中的ETag格式为inode-size-timestamp。即使某个文件在不同的服务器上会处于相同的目录下,文件大小、权限、时间戳等都完全相同,但是在不同服务器上他们的内码也是不同的。 IIS 5.0和IIS 6.0处理ETag的机制相似。IIS中的ETag格式为Filetimestamp:ChangeNumber。用ChangeNumber来跟踪IIS配置的改变。网站所用的不同IIS服务器间ChangeNumber也不相同。 不同的服务器上的Apache和IIS即使对于完全相同的内容产生的ETag在也不相同,用户并不会接收到一个小而快的304响应;相反他们会接收一个正常的200响应并下载全部内容。如果你的网站只放在一台服务器上,就不会存在这个问题。但是如果你的网站是架设在多个服务器上,并且使用Apache和IIS产生默认的ETag配置,你的用户获得页面就会相对慢一点,服务器会传输更多的内容,占用更多的带宽,代理也不会有效地缓存你的网站内容。即使你的内容拥有Expires文件头,无论用户什么时候点击“刷新”或者“重载”按钮都会发送相应的GET请求。 如果你没有使用ETag提供的灵活的验证模式,那么干脆把所有的ETag都去掉会更好。Last-Modified文件头验证是基于内容的时间戳的。去掉ETag文件头会减少响应和下次请求中文件的大小。微软的这篇支持文稿讲述了如何去掉ETag。在Apache中,只需要在配置文件中简单添加下面一行代码就可以了: FileETag none 15、尽早刷新输出缓冲 当用户请求一个页面时,无论如何都会花费200到500毫秒用于后台组织HTML文件。在这期间,浏览器会一直空闲等待数据返回。在PHP中,你可以使用flush()方法,它允许你把已经编译的好的部分HTML响应文件先发送给浏览器,这时浏览器就会可以下载文件中的内容(脚本等)而后台同时处理剩余的HTML页面。这样做的效果会在后台烦恼或者前台较空闲时更加明显。 输出缓冲应用最好的一个地方就是紧跟在<head />之后,因为HTML的头部分容易生成而且头部往往包含CSS和JavaScript文件,这样浏览器就可以在后台编译剩余HTML的同时并行下载它们。 例子: … <!– css, js –> </head> <?php flush(); ?> <body> … <!– content –> 为了证明使用这项技术的好处,Yahoo!搜索率先研究并完成了用户测试。 16、使用GET来完成AJAX请求 Yahoo!Mail团队发现,当使用XMLHttpRequest时,浏览器中的POST方法是一个“两步走”的过程:首先发送文件头,然后才发送数据。因此使用GET最为恰当,因为它只需发送一个TCP包(除非你有很多cookie)。IE中URL的最大长度为2K,因此如果你要发送一个超过2K的数据时就不能使用GET了。 一个有趣的不同就是POST并不像GET那样实际发送数据。根据HTTP规范,GET意味着“获取”数据,因此当你仅仅获取数据时使用GET更加有意义(从语意上讲也是如此),相反,发送并在服务端保存数据时使用POST。
转载请注明原创地址:http://www.cnblogs.com/softlover/archive/2012/11/20/2779893.html 在上一讲中,我们的解决方案使用到了jquery去创建一个span标签。在这一讲中我们将用一种更好的解决方式,使用:before 和 :after 伪类。:before经常会用到,他可以用来添加额外的元素。 demo预览地址:http://webdesignerwall.com/demo/decorative-gallery-2/ HTML 下面是一个ul列表代表的图片画廊。 <ul class="gallery clip"> <li> <img src="http://webdesignerwall.com/wp-content/uploads/2012/09/sample-1.jpg" alt="image"> </li> <li> <img src="http://webdesignerwall.com/wp-content/uploads/2012/09/sample-2.jpg" alt="image"> </li> <li> <img src="http://webdesignerwall.com/wp-content/uploads/2012/09/sample-1.jpg" alt="image"> </li> </ul> CSS 下面是为.gallery设置的css,这里需要注意的一点是,我们需要为.gallery下面的a标签设置position: relative。 .gallery { margin: 0 0 25px; text-align: center; } .gallery li { display: inline-block; margin: 5px; list-style: none; } .gallery a { position: relative; display: inline-block; } :before元素 我们将会为 :before 元素指定一个30 x 60px大小的曲别针背景图片。注意到我将css的content属性设为空值。没有空的content属性,容器就不会显示。 .clip a:before { position: absolute; content: ' '; top: -5px; left: -4px; width: 30px; height: 60px; background: url(http://webdesignerwall.com/wp-content/uploads/2012/09/paper-clip.png) no-repeat; } 艺术边框 利用这种技术,你可以再图片上添加任意的遮罩效果。下面的例子,我把图片背景换成了艺术边框。 .frame a:before { position: absolute; content: ' '; top: -22px; left: -23px; width: 216px; height: 166px; background: url(http://webdesignerwall.com/wp-content/uploads/2012/09/frame.png) no-repeat; } HTML5画廊 我们可以使用html5标签,创造更高级的画廊。下面的例子,我们使用<figure>包装图片,<figcaption>包含图片标题。 <ul class="gallery tape"> <li> <figure> <img src="http://webdesignerwall.com/wp-content/uploads/2012/09/sample-4.jpg" alt="image"> <figcaption>Image Caption</figcaption> </figure> </li> <li> <figure> <img src="http://webdesignerwall.com/wp-content/uploads/2012/09/sample-5.jpg" alt="image"> <figcaption>Image Caption</figcaption> </figure> </li> <li> <figure> <img src="http://webdesignerwall.com/wp-content/uploads/2012/09/sample-6.jpg" alt="image"> <figcaption>Image Caption</figcaption> </figure> </li> </ul> CSS css中我添加了两个:before,一个针对<figure>元素,另一个针对<li>元素。遮罩图片overlay.png被用在了figure:before上面,胶带图片用在了 a:before上面。 .tape li { width: 170px; padding: 5px; margin: 15px 10px; border: solid 1px #cac09f; background: #fdf8e4; text-align: center; box-shadow: inset 0 1px rgba(255,255,255,.8), 0 1px 2px rgba(0,0,0,.2); } .tape figure { position: relative; margin: 0; } .tape a:before { position: absolute; content: ' '; top: 0; left: 0; width: 100%; height: 100%; background: url(http://webdesignerwall.com/wp-content/uploads/2012/09/overlay.png) no-repeat; } .tape figcaption { font: 100%/120% Handlee, Arial, Helvetica, sans-serif; color: #787568; } .tape a:before { position: absolute; z-index: 2; content: ' '; top: -12px; left: 50%; width: 115px; height: 32px; margin-left: -57px; background: url(http://webdesignerwall.com/wp-content/uploads/2012/09/tape.png) no-repeat; } CSS3 Transform 在这个例子中,我使用了软木纹饰背景,并使用transform属性转变图片。 .transform { background: url(http://webdesignerwall.com/wp-content/uploads/2012/09/cork-bg.png); padding: 25px; border-radius: 10px; box-shadow: inset 0 1px 5px rgba(0,0,0,.4); } .transform li { border: none; } Nth-of-Type 为了让图片旋转的更随机和自然,我使用nth-of-type去筛选图片,为不同图片设置不同的旋转角度。 .transform li:nth-of-type(4n+1) { -webkit-transform: rotate(2deg); } .transform li:nth-of-type(2n) { -webkit-transform: rotate(-1deg); } .transform li:nth-of-type(4n+3) { -webkit-transform: rotate(2deg); } 好了,今天的教程到此为止。 原文地址:http://webdesignerwall.com/tutorials/decorative-css-gallery-part-2 HTML5实践系列
转载请注明原创地址:http://www.cnblogs.com/softlover/archive/2012/11/20/2779890.html 本节课我们将介绍,如何使用css在不修改图片源的前提下装饰你的图片画廊。这里用到的技巧也很简单,就是在图片之前创建一个<span>,并在span上使用background-image生成一个遮罩的效果。这种方式既简单又灵活,demo中介绍了20多种样式,大家可以参看。 demo预览地址:http://www.webdesignerwall.com/demo/decorative-gallery/decorative-gallery-index.html?TB_iframe=true&height=550&width=840 demo下载地址:http://www.webdesignerwall.com/file/decorative-gallery-demo.zip 这种css技巧带来的好处 节约时间 — 你不需要在photoshop中创建图片模板,然后为每张图片生成独立文件。 保留原始图片源 — 我们不用担心日后需要更换图片主题的时候,没有原图片。因为我们根本没有修改他。 相当的灵活 — 你只需要调整css就能换一个完全不同的样式。 在任何站点都能工作 — 这个css技巧在任何站点,任何图片大小下都适用。 解决浏览器兼容问题 — 通过了大多数浏览器的测试 (Firefox, Safari, Opera, 甚至包括行为怪异的IE6)。 基本概念 我们需要在包裹img的div中创建一个span元素,在他上面适用background-image来产生遮罩的效果。如果你不喜欢插入一个空的span标签,我们可以使用javascript来动态生成他,之后我们会介绍到。下面的代码揭示了他如何工作。 对于css代码,我们需要注意的是,需要为div设置position:relative,为span设置 position:absolute。这样你就可以通过为span设置top和left属性,来任意摆布他在div中的位置了。 IE PNG hack 为了能让透明的png图片在ie6中工作,我们需要使用到强大的 iepngfix.htc hack。下载 iepngfix.htc 文件,并在页面<head>标签中添加如下代码。 <!--[if lt IE 7]> <style type="text/css"> .photo span { behavior: url(iepngfix.htc); } </style> <![endif]--> 外观 我们只需要修改指定span元素的css,就能实现不同样式的变化。查看demo源代码,你会发现其中的奥秘。 jquery解决方案 如果你不喜欢在页面中直接添加空的span标签,可以使用下面的jquery代码,实现span的动态添加。 <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript"> $(document).ready(function(){ //预添加 span 标签 $(".photo a").prepend("<span></span>"); }); </script> #1. 简单画廊 让我们使用之前介绍的技术,来创建画廊吧。 #1b. 迷你icon 这个例子为了展示了,如何在图片上面放置不同的icon。我们需要为span标签设置不同的css class 名称。 #2. 带文字的图片 该例子为了展示了如何创建带文字连接的画廊。 #2b. 弹出文字 #3. 迷你纸夹 #4. 软木板画廊 sIFR版本(文字替换) 在这个版本中借助 sIFR, 实现将em中的文字替换为手写体。 #4b.胶带效果 #5.黑框艺术画廊 #5b.金边艺术画廊 #6.水彩效果 sIFR版本 #7.高光效果 #8.木板画廊 最后我们展示如何使用background-image来实现木板的效果。 最后评论 我们可以看到这种css技巧相当的灵活,他极具创造性,他将图片和css有机的结合在了一起。你也可以使用这种方式,创建自己不同风格的画廊。 原文地址:http://webdesignerwall.com/tutorials/css-decorative-gallery HTML5实践系列
作为Web设计师,你的网站在各种浏览器中有完全一样的表现是很多人的目标,然而这是一个永远无法真正实现的目标,很多人认为,完美的跨浏览器兼容并不必要,这样说虽然没错,但在很多情形,一种近似的兼容还是很容易实现的。 51CTO推荐阅读:Web标准化 跨浏览器开发教程 理解CSS盒子模型 如果你想实现不需要很多奇巧淫技的跨浏览器兼容的 CSS 代码,透彻地理解 CSS 盒子模型是首要事情,CSS 盒子模型并不难,且基本支持所有浏览器,除了某些特定条件下的 IE 浏览器。CSS 盒子模型负责处理以下事情: ◆一个blcok(区块)级对象占据多大的空间 ◆该对象的边界,留白 ◆盒子的尺寸 ◆盒子与页面其它元素的相对位置 CSS 盒子模型有以下准则: ◆Block (区块)对象都是矩形 (事实上所有对象都如此) ◆其尺寸由 width, height, padding, borders, 以及margins决定 ◆如果不设置高度,该盒子的高度将自动适应其包含的内容,加上留白等(除非使用了 float) ◆如果不设置宽度,一个非 float 型盒子水平上将充满其父容器(扣除父容器的留白) ◆处理 block 级对象时,必须注意以下事项: ◆如果一个盒子的宽度设置为 100%,它就不能再设置 margins, padding, 和 borders,否则会撑破其父容器 ◆垂直毗邻的 margin 会引起复杂的坍塌问题,导致布局问题 ◆拥有相对位置和绝对位置的对象,拥有不同的行为 在 Firefox 的 Firebug 中显示的盒子模型 理解 block 级和 inline 级 对象的区别。这个看似简单的问题事如果能透彻地理解,会受益匪浅。 下图讲解了 block 级对象和 inline 级对象的区别: 下面是 block 级对象和 inline 级对象的基本区别: ◆Block 级对象会自然地水平充满其父容器,因此没有必要为之设置 100% 宽度属性。 ◆Block 级对象的起始摆放位置是其父容器的左上边界,并顺排在其前面的兄弟 Block 对象的下方(除非设置 float 或绝对位置)。 ◆Inline 级对象会忽略其宽度和高度设置。 ◆Inline 级对象会随着文字排版,并受排版属性的影响(如 white-space, font-size, letter-spacing)。 ◆Inline 级对象可以使用 vertical-align 属性控制其垂直对齐,block 级对象不可以。 ◆Inline 级对象的下方会保留一些自然的空间,以适应字母 g 一类的会向下探出的笔画。 ◆一个设置为 float 的 inline 对象将变成 block 对象理解Floating和Clearing属性。 实现多栏排版的最好方法是使用float属性,float也是一个将使你受益匪浅的属性。一个 float 对象可以居左或居右,一个设置为 float 的对象,将根据设置的方向,左移或右移到其父容器的边界,或其前面的 float 对象的边界,而紧随其后的非 float 对象或内容,则包围在其相反的方向。 以下是使用float和clear属性的一些重要准则: ◆一个 float对象,将从其置身的 block 级非 float 内容流中跳出,换句话说,如果你要将一个 box 向左边 float,它后面的 block 级非 float 对象会显示到下方,inline级内容会在旁边包围。 ◆要让一段内容从一侧包围一个 float 对象,这段内容必须要么是 inline 级的,要么也设置为相同方向的 float。 ◆一个 float 对象,如果没有设置宽度,则会自动缩成其包含的内容的宽度,因此最好为 float 对象明确设置宽度。 ◆如果一个 block 对象包含 float 子对象,会出现本文中阐述的问题。 ◆一个设置了 clear 属性的对象,将不会包围其前面的 float 对象。 ◆一个既设置了 clear 又设置了 float 属性的对象,只有 clear:left 属性生效,clear:right 不起作用首先使用 IE 进行测试。 虽然我们都痛恨 IE6 和 IE7,但当你开始一个新项目的时候,最好还是首先针对这两种浏览器进行测试,否则,如果你在设计在后期才想起针对 IE6 和 IE7 进行测试,将出现以下问题: ◆你将不得不使用一些奇巧淫技,甚至使用独立的 IE6/7 CSS,导致 CSS 文件臃肿。 ◆某些地方的布局将不得不重新设计。 ◆会增加测试的时间。 ◆你的布局在 IE/6/7 中和其它浏览器中不一样 如果你设计的是个人项目,Web 程序等,则不建议你针对旧版本 IE 做太多工作,而对一些公司类站点,它的用户群中有大量 IE 用户,这些技巧会让你避免大量的头痛。如果将 IE 的问题归类为 IE 的 BUG 而不去处理,会带来很多负面的影响,和 IE 和平共处是 Web 开发与设计者不可逃避的现实。 译者注:在 IE6/7 仍有大量用户基础的国内(感谢中行,建行,农行,工行,以及各级政府网站),忽视这两种浏览器是极不明智的,首先针对 IE6/7 进行设计是一种很好的方法,一般来说,在IE6/7 通过测试的站点,在 Firefox,Chrome,Safari,Opera 等标准浏览器面前基本不会出现问题,前提是,你的 CSS 设计是基于 W3C 标准的。 IE浏览器最常见的问题 ◆IE6中不可滥用 float,否则会带来内容消失以及文字重复等稀奇古怪的问题。 ◆IE6 中,float 对象,在 float 方向的那边,会出现双倍 margin,将 display 设置为 inline 会解决这个问题。 ◆IE6/7 中,一个没有直接或间接设置 hasLayout 的对象,会发生各种稀奇古怪的问题。 ◆IE6 不支持 min-width, max-width, min-height, max-height 一类的属性。 ◆IE6 不支持固定位置背景图。 ◆IE6/7 不支持很多 display 属性值(如 inline-table, table-cell, table-row)。 ◆IE6 中,只有 a 这个对象才可以使用 :hover 这个伪类。 ◆IE 的某些版本对某些 CSS 选择器支持很少(如属性选择器,子对象选择器)。 ◆IE6~8 对 CSS3 的支持很有限 (不过有一些变通方法) 永远不要指望在所有浏览器中都一模一样。 ◆在不同浏览器实现相同的体验个功能是可能的,实现近似像素级的一致外观也是可能的,但永远不要指望一模一样。 Form控件在不同浏览器显示总是不同 以下是Facebook首页中的select控件,在5种不同浏览器的显示差异(基于 Adobe’s Browserlab 截图) 某些Form控件,如果要求必须跨浏览器一致,可以找到变通办法,如,可以使用图片替代submit按钮,但有一些控件,比如 radio,select, textarea,文件选择框,是永远都不可能一模一样的。 字体的表现都有差异 先不谈有的字体在有的系统中根本不存在,即时存在,它们在不同系统的渲染效果也不完全一样,比如,Windows ClearType 支持 IE7,但不支持 IE6,导致同一个字体在 IE7 和 IE6 有不同的样子。 使用CSS清零 使用CSS清零是实现跨浏览器兼容的灵丹妙药,CSS清零可以消除不同浏览器对margin,padding这些属性的默认表现,你可以更容易控制诸如对齐,间隙等等问题。 结语 跨浏览器兼容是个永恒的话题,本文介绍的跨浏览器兼容 CSS 准则只是帮助 Web 开发设计者尽可能实现这一目标,除了这些,基于CSS 3的渐进式增强设计也是一种趋势,Web 开发与设计者可以针对某些浏览器提供增强功能,而在不支持这些增强功能的浏览器中降级使用基本功能。
原文地址:http://blog.163.com/seo_luofeng/blog/static/17657502420112302540348/?suggestedreading&wumii 从事跟网站有关的懂行的人一般都会使用firefox浏览器,那肯定离不开firefox下面强大的插件,而Firebug是Firefox下的一款开发类插件,现属于Firefox的 五星级强力推荐插件之一。它集HTML查看和编辑、Javascript控制台、网络状况监视器于一体,是开发JavaScript、CSS、HTML和 Ajax的得力助手。Firebug如同一把精巧的瑞士军刀,从各个不同的角度剖析Web页面内部的细节层面,给Web开发者带来很大的便利。唯一不方便的是firebug的扩展基本是英文的. Firebug是革命性的Firefox扩展,帮助网页开发者与设计师以及seo等技术人员测试检查前端代码。为我们提供了大量有用的特色功能,比如,延迟信息控制板,DOM查看器,页面元素的逐条信息,以及更多。尽管Furebug 已经是捆绑了大量开发功能的工具箱了,但是仍然有几个扩展可以提升它的可用性。本文为大家推荐介绍十款最好Firebug扩展(顾名思义,就是整合到插件firebug里的东西,清爽又省位置),让开发生活更加轻松。最重要的是它们竟然还是免费的.为方便各位安装扩展,我在每个扩展介绍后面都提供了下载链接. 1. Pixel Perfect Pixel Perfect可以把网页与设计稿覆盖在一起组成一个网页,方便精确的编写CSS与HTML。借由切换网页组成的开关,网页开发者与设计师拥有可视化指导,精确位置和网页部件的尺寸。查看 视频演示,看看Pixel Perfect如何工作的。 安装 Pixel Perfect 2. Page Speed Page Speed是评估网页表现,为开发者提供可行的前端性能优化建议的开源Firebug插件。测试和评估是基于Google的Steve Sounder编写的网页性能的最优方法。请务必阅读页面速度用户指南完整文档的许多功能。 安装 Page Speed 3. CodeBurner CodeBurner,sitepoint发布的Firebug的扩展,提供内建的HTML和CSS参考。扩展也显示基于当前CSS 和HTML面板前后关系的信息。这样的参考非常实用,为你显示浏览器兼容性与W3C网页元素推荐遵守协议的信息,其中很多其他类型的信息。 安装 CodeBurner 4.FireRainbow FireRainbow是简单的Firebug扩展,提供了非常联想的功能:代码语法高亮。FireRainbow把JavaScript, CSS, 和HTML变成了彩色,提供了更好的代码可读性,方便在Firebug回顾与审查。现在有超过二十种可供选择的FireRainbow主题安装,可以自由定制。 安装 FireRainbow 5.Inline Code Finder Inline Code Finder 非常适合查看内嵌的avaScript和CSS,适合开发者重构现有的标记以单独的结构(HTML)与风格(CSS)以及功能(JavaScript) 。该工具使用很简单:搜索整个网页的内嵌代码,为开发人员提供内联代码前后关系的信息。你可以过滤某些群体内嵌代码。 安装 Inline Code Finder 6. SenSEO SenSEO是分析网页并指出如何做白帽搜索引擎优化的Firebug扩展。核查元标记的正确使用,存在的题目,标题,和其他相关的最佳搜索引擎优化标准,这个你会喜欢的,一键看清所有元素. 安装SenSEO 7.YSlow YSlow评价网页的性能,并建议改善有潜力的地方。 YSlow是基于YDN的加快网站的最佳方法,并给你三个预定义(或用户定义的)规则设定。它有几个有用的功能,如显示的信息和网页组件的资料统计,整合了优化工具,如JSLint 和Smush it。 安装YSlow 8.Firefinder Firefinder是按照您输入搜索标准快速查找网页内容中相匹配的CSS或XPath选择符。 Firefinder对测试哪个网页内容受CSS样式规则影响,高亮与查找符合您的搜索的元素非常实用。 安装 Firefinder firebug是很优秀的开发插件,另外还有几个firefox插件也很优秀,比如webdeveloper,seoquake,search status以及一些插件同步,书签同步插件,非常方便使用.落枫seo今天就先提一下,有兴趣又没用过的人可以去试试看,只要你会用(由于这些插件基本都是英文的,所有最好有点英文功底),保证你会爱上它.感谢这些插件的开发者
CSS对浏览器的兼容性有时让人很头疼,或许当你了解当中的技巧跟原理,就会觉得也不是难事,从网上收集了IE7,6与Fireofx的兼容性处理 方法并整理了一下。对于web2.0的过度,请尽量用xhtml格式写代码,而且DOCTYPE 影响 CSS 处理,作为W3C的标准,一定要加 DOCTYPE声明。 CSS技巧1.div的垂直居中问题vertical-align:middle; 将行距增加到和整个DIV一样高 line-height:200px; 然后插入文字,就垂直居中了。缺点是要控制内容不要换行 2. margin加倍的问题 设置为float的div在ie下设置的margin会加倍。这是一个ie6都存在的bug。解决方案是在这个div里面加上display:inline; 例如: <#div id=”imfloat”> 相应的css为 #imfloat{ float:left; margin:5px;/*IE下理解为10px*/ display:inline;/*IE下再理解为5px*/} 3.浮动ie产生的双倍距离 #box{ float:left; width:100px; margin:0 0 0 100px; //这种情况之下IE会产生200px的距离 display:inline; //使浮动忽略} 这里细说一下block与inline两个元素:block元素的特点是,总是在新行上开始,高度,宽度,行高,边距都可以控制(块元素);Inline元素的特点是,和其他元素在同一行上,不可控制(内嵌元素); #box{ display:block; //可以为内嵌元素模拟为块元素 display:inline; //实现同一行排列的效果 diplay:table; 4 IE与宽度和高度的问题 IE 不认得min-这个定义,但实际上它把正常的width和height当作有min的情况来使。这样问题就大了,如果只用宽度和高度,正常的浏览器里这两 个值就不会变,如果只用min-width和min-height的话,IE下面根本等于没有设置宽度和高度。 比如要设置背景图片,这个宽度是比较重要的。要解决这个问题,可以这样: #box{ width: 80px; height: 35px;}html>body #box{ width: auto; height: auto; min-width: 80px; min-height: 35px;} 5.页面的最小宽度 min- width是个非常方便的CSS命令,它可以指定元素最小也不能小于某个宽度,这样就能保证排版一直正确。但IE不认得这个,而它实际上把 width当做最小宽度来使。为了让这一命令在IE上也能用,可以把一个<div> 放到 <body> 标签下,然后为div指定一个类,然后CSS这样设计: #container{ min-width: 600px; width:expression(document.body.clientWidth < 600? "600px": "auto" );} 第一个min-width是正常的;但第2行的width使用了Javascript,这只有IE才认得,这也会让你的HTML文档不太正规。它实际上通过Javascript的判断来实现最小宽度。 6.DIV浮动IE文本产生3象素的bug 左边对象浮动,右边采用外补丁的左边距来定位,右边对象内的文本会离左边有3px的间距. #box{ float:left; width:800px;} #left{ float:left; width:50%;} #right{ width:50%;} *html #left{ margin-right:-3px; //这句是关键} <div id="box"> <div id="left"></div> <div id="right"></div> </div> 7.IE捉迷藏的问题 当div应用复杂的时候每个栏中又有一些链接,DIV等这个时候容易发生捉迷藏的问题。 有些内容显示不出来,当鼠标选择这个区域是发现内容确实在页面。 解决办法:对#layout使用line-height属性 或者给#layout使用固定高和宽。页面结构尽量简单。 8.float的div闭合;清除浮动;自适应高度; ① 例如:<#div id=”floatA” ><#div id=”floatB” ><#div id=”NOTfloatC” >这里的NOTfloatC并不希望继续平移,而是希望往下排。(其中floatA、floatB的属性已经设置为float:left;) 这 段代码在IE中毫无问题,问题出在FF。原因是NOTfloatC并非float标签,必须将float标签闭合。在 <#div class=”floatB”> <#div class=”NOTfloatC”>之间加上 <#div class=”clear”>这个div一定要注意位置,而且必须与两个具有float属性的div同级,之间不能存在嵌套关系,否则会产生异常。 并且将clear这种样式定义为为如下即可: .clear{ clear:both;} ②作为外部 wrapper 的 div 不要定死高度,为了让高度能自动适应,要在wrapper里面加上overflow:hidden; 当包含float的box的时候,高度自动适应在IE下无效,这时候应该触发IE的layout私有属性(万恶的IE啊!)用zoom:1;可以做到,这 样就达到了兼容。 例如某一个wrapper如下定义: .colwrapper{ overflow:hidden; zoom:1; margin:5px auto;} ③对于排版,我们用得最多的css描述可能就是float:left.有的时候我们需要在n栏的float div后面做一个统一的背景,譬如: <div id=”page”> <div id=”left”></div> <div id=”center”></div> <div id=”right”></div> </div> 比 如我们要将page的背景设置成蓝色,以达到所有三栏的背景颜色是蓝色的目的,但是我们会发现随着left center right的向下拉长,而page居然保存高度不变,问题来了,原因在于page不是float属性,而我们的page由于要居中,不能设置成 float,所以我们应该这样解决 <div id=”page”> <div id=”bg” style=”float:left;width:100%”> <div id=”left”></div> <div id=”center”></div> <div id=”right”></div> </div> </div> 再嵌入一个float left而宽度是100%的DIV解决之 ④万能float 闭合(非常重要!) 关 于 clear float 的原理可参见 [How To Clear Floats Without Structural Markup],将以下代码加入Global CSS 中,给需要闭合的div加上 class="clearfix" 即可,屡试不爽. /* Clear Fix */ .clearfix:after { content:"."; display:block; height:0; clear:both; visibility:hidden; } .clearfix { display:inline-block; } /* Hide from IE Mac */ .clearfix {display:block;} /* End hide from IE Mac */ /* end of clearfix */ 或者这样设置:.hackbox{ display:table; //将对象作为块元素级的表格显示} 9.高度不适应 高度不适应是当内层对象的高度发生变化时外层高度不能自动进行调节,特别是当内层对象使用margin 或paddign 时。 例: #box {background-color:#eee; } #box p {margin-top: 20px;margin-bottom: 20px; text-align:center; } <div id="box"> <p>p对象中的内容</p> </div> 解决方法:在P对象上下各加2个空的div对象CSS代码:.1{height:0px;overflow:hidden;}或者为DIV加上border属性。10 .IE6下为什么图片下有空隙产生 解 决这个BUG的方法也有很多,可以是改变html的排版,或者设置img 为display:block 或者设置vertical-align 属性为vertical-align:top | bottom |middle |text-bottom 都可以解决. 11.如何对齐文本与文本输入框 加上 vertical-align:middle; <style type="text/css"> <!-- input { width:200px; height:30px; border:1px solid red; vertical-align:middle; } --> </style>12.web标准中定义id与class有什么区别吗 一.web标准中是不容许重复ID的,比如 div id="aa" 不容许重复2次,而class 定义的是类,理论上可以无限重复, 这样需要多次引用的定义便可以使用他. 二.属性的优先级问题 ID 的优先级要高于class,看上面的例子 三.方便JS等客户端脚本,如果在页面中要对某个对象进行脚本操作,那么可以给他定义一个ID,否则只能利用遍历页面元素加上指定特定属性来找到它,这是相对浪费时间资源,远远不如一个ID来得简单. 13. LI中内容超过长度后以省略号显示的方法 此方法适用与IE与OP浏览器 <style type="text/css"> <!-- li { width:200px; white-space:nowrap; text-overflow:ellipsis; -o-text-overflow:ellipsis; overflow: hidden; } --> </style>14.为什么web标准中IE无法设置滚动条颜色了 解决办法是将body换成html <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <meta http-equiv="Content-Type" content="text/html; charset=gb2312" /> <style type="text/css"> <!-- html { scrollbar-face-color:#f6f6f6; scrollbar-highlight-color:#fff; scrollbar-shadow-color:#eeeeee; scrollbar-3dlight-color:#eeeeee; scrollbar-arrow-color:#000; scrollbar-track-color:#fff; scrollbar-darkshadow-color:#fff; } --> </style> 15.为什么无法定义1px左右高度的容器 IE6下这个问题是因为默认的行高造成的,解决的方法也有很多,例如:overflow:hidden | zoom:0.08 | line-height:1px16.怎么样才能让层显示在FLASH之上呢 解决的办法是给FLASH设置透明 <param name="wmode" value="transparent" />17.怎样使一个层垂直居中于浏览器中 这里我们使用百分比绝对定位,与外补丁负值的方法,负值的大小为其自身宽度高度除以二 <style type="text/css"> <!-- div { position:absolute; top:50%; lef:50%; margin:-100px 0 0 -100px; width:200px; height:200px; border:1px solid red; } --> </style> FF与IE 1. Div居中问题 div设置 margin-left, margin-right 为 auto 时已经居中,IE 不行,IE需要设定body居中,首先在父级元素定义text-algin: center;这个的意思就是在父级元素内的内容居中。 2.链接(a标签)的边框与背景 a 链接加边框和背景色,需设置 display: block, 同时设置 float: left 保证不换行。参照 menubar, 给 a 和 menubar 设置高度是为了避免底边显示错位, 若不设 height, 可以在 menubar 中插入一个空格。3.超链接访问过后hover样式就不出现的问题 被点击访问过的超链接样式不在具有hover和active了,很多人应该都遇到过这个问题,解决方法是改变CSS属性的排列顺序: L-V-H-A Code: <style type="text/css"> <!-- a:link {} a:visited {} a:hover {} a:active {} --> </style> 4. 游标手指cursor cursor: pointer 可以同时在 IE FF 中显示游标手指状, hand 仅 IE 可以 5.UL的padding与margin ul标签在FF中默认是有padding值的,而在IE中只有margin默认有值,所以先定义 ul{margin:0;padding:0;}就能解决大部分问题 6. FORM标签 这 个标签在IE中,将会自动margin一些边距,而在FF中margin则是0,因此,如果想显示一致,所以最好在css中指定margin和 padding,针对上面两个问题,我的css中一般首先都使用这样的样式ul,form{margin:0;padding:0;}给定义死了,所以后 面就不会为这个头疼了. 7. BOX模型解释不一致问题 在FF和IE中 的BOX模型解释不一致导致相差2px解决方法:div{margin:30px!important;margin:28px;} 注意这两个margin的顺序一定不能写反, important这个属性IE不能识别,但别的浏览器可以识别。所以在IE下其实解释成这样: div{maring:30px;margin:28px}重复定义的话按照最后一个来执行,所以不可以只写margin:xx px!important; #box{ width:600px; //for ie6.0- w\idth:500px; //for ff+ie6.0} #box{ width:600px!important //for ff width:600px; //for ff+ie6.0 width /**/:500px; //for ie6.0-} 8.属性选择器(这个不能算是兼容,是隐藏css的一个bug) p[id]{}div[id]{} 这个对于IE6.0和IE6.0以下的版本都隐藏,FF和OPera作用.属性选择器和子选择器还是有区别的,子选择器的范围从形式来说缩小了,属性选择器的范围比较大,如p[id]中,所有p标签中有id的都是同样式的. 9.最狠的手段 - !important; 如果实在没有办法解决一些细节问题,可以用这个方法.FF对于”!important”会自动优先解析,然而IE则会忽略.如下 .tabd1{ background:url(/res/images/up/tab1.gif) no-repeat 0px 0px !important; /*Style for FF*/ background:url(/res/images/up/tab1.gif) no-repeat 1px 0px; /* Style for IE */} 值得注意的是,一定要将xxxx !important 这句放置在另一句之上,上面已经提过 10.IE,FF的默认值问题 或 许你一直在抱怨为什么要专门为IE和FF写不同的CSS,为什么IE这样让人头疼,然后一边写css,一边咒骂那个可恶的M$ IE.其实对于css的标准支持方面,IE并没有我们想象的那么可恶,关键在于IE和FF的默认值不一样而已,掌握了这个技巧,你会发现写出兼容FF和 IE的css并不是那么困难,或许对于简单的css,你完全可以不用”!important”这个东西了。 我们都知道,浏览器在显示网页 的时候,都会根据网页的css样式表来决定如何显示,但是我们在样式表中未必会将所有的元素都进行了具体的描述,当然也没有必要那么做,所以对于那些没有 描述的属性,浏览器将采用内置默认的方式来进行显示,譬如文字,如果你没有在css中指定颜色,那么浏览器将采用黑色或者系统颜色来显示,div或者其他 元素的背景,如果在css中没有被指定,浏览器则将其设置为白色或者透明,等等其他未定义的样式均如此。所以有很多东西出现 FF和IE显示不一样的根本原因在于它们的默认显示不一样,而这个默认样式该如何显示我知道在w3中有没有对应的标准来进行规定,因此对于这点也就别去怪 罪IE了。11.为什么FF下文本无法撑开容器的高度 标准浏览器中固定高度值的容器是不会象IE6里那样被撑开的,那我又想固定高度,又想能被撑开需要怎样设置呢?办法就是去掉height设置min- height:200px; 这里为了照顾不认识min-height的IE6 可以这样定义: { height:auto!important; height:200px; min-height:200px; } 12.FireFox下如何使连续长字段自动换行 众所周知IE中直接使用 word-wrap:break-word 就可以了, FF中我们使用JS插入&#10;的方法来解决 <style type="text/css"> <!-- div { width:300px; word-wrap:break-word; border:1px solid red; } --> </style> <div id="ff">aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</div> <scrīpt type="text/javascrīpt"> /* <![CDATA[ */ function toBreakWord(el, intLen){ var ōbj=document.getElementById(el); var strContent=obj.innerHTML; var strTemp=""; while(strContent.length>intLen){ strTemp+=strContent.substr(0,intLen)+"&#10;"; strContent=strContent.substr(intLen,strContent.length); } strTemp+="&#10;"+strContent; obj.innerHTML=strTemp; } if(document.getElementById && !document.all) toBreakWord("ff", 37); /* ]]> */ </scrīpt>13.为什么IE6下容器的宽度和FF解释不同呢 <?xml version="1.0" encoding="gb2312"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <meta http-equiv="Content-Type" content="text/html; charset=gb2312" /> <style type="text/css"> <!-- div { cursor:pointer; width:200px; height:200px; border:10px solid red } --> </style> <div ōnclick="alert(this.offsetWidth)">让FireFox与IE兼容</div> 问 题的差别在于容器的整体宽度有没有将边框(border)的宽度算在其内,这里IE6解释为200PX ,而FF则解释为220PX,那究竟是怎么导致的问题呢?大家把容器顶部的xml去掉就会发现原来问题出在这,顶部的申明触发了IE的qurks mode,关于qurks mode、standards mode的相关知识,请参考:http://www.microsoft.com/china/msdn/library/webservices /asp.net/ ASPNETusStan.mspx?mfr=true IE6,IE7,FF IE7.0出来了,对CSS的支持又有新问题。浏览器多了,网Bpx; /*For IE7 & IE6*/ _height:20px; /*For IE6*/ 注意顺序。 这样也属于CSS HACK,不过没有上面这样简洁。 #example { color: #333; } /* Moz */ * html #example { color: #666; } /* IE6 */ *+html #example { color: #999; } /* IE7 */ 第二种,是使用IE专用的条件注释 <!--其他浏览器 --> <link rel="stylesheet" type="text/css" href="http://blog.163.com/seo_luofeng/blog/css.css" /> <!--[if IE 7]> <!-- 适合于IE7 --> <link rel="stylesheet" type="text/css" href="http://blog.163.com/seo_luofeng/blog/ie7.css" /> <![endif]--> <!--[if lte IE 6]> <!-- 适合于IE6及一下 --> <link rel="stylesheet" type="text/css" href="http://blog.163.com/seo_luofeng/blog/ie.css" /> <![endif]--> 第三种,css filter的办法,以下为经典从国外网站翻译过来的。. 新建一个css样式如下: #item { width: 200px; height: 200px; background: red; } 新建一个div,并使用前面定义的css的样式: <div id="item">some text here</div> 在body表现这里加入lang属性,中文为zh: <body lang="en"> 现在对div元素再定义一个样式: *:lang(en) #item{ background:green !important; } 这样做是为了用!important覆盖原来的css样式,由于:lang选择器ie7.0并不支持,所以对这句话不会有任何作用,于是也达到了 ie6.0下同样的效果,但是很不幸地的是,safari同样不支持此属性,所以需要加入以下css样式: #item:empty { background: green !important } :empty选择器为css3的规范,尽管safari并不支持此规范,但是还是会选择此元素,不管是否此元素存在,现在绿色会现在在除ie各版本以外的浏览器上。 对IE6和FF的兼容可以考虑以前的!important 个人比较喜欢用第一种,简洁,兼容性比较好。
原文地址:http://www.nowamagic.net/librarys/veda/detail/634 浏览器可以分为两部分,shell+内核。其中shell的种类相对比较多,内核则比较少。Shell是指浏览器的外壳:例如菜单,工具栏等。主要是提供给用户界面操作,参数设置等等。它是调用内核来实现各种功能的。内核才是浏览器的核心。内核是基于标记语言显示内容的程序或模块。 内核又可以分成两部分:渲染引擎(layout engineer或者Rendering Engine)和JS引擎。它负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入CSS等),以及计算网页的显示方式,然后会输出至 显示器或打印机。浏览器的内核的不同对于网页的语法解释会有不同,所以渲染的效果也不相同。所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要内核。(参见维基百科)JS引擎则是解析Javascript语言,执行javascript语言来实现网页的动态效果。最开始渲染引擎和JS引擎并没有区分的很明确,后来JS引擎越来越独立,内核就倾向于只指渲染引擎。 四种主流浏览器内核 浏览器的页面渲染引擎负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入CSS等),以及计算网页的显示方式,然后会输出至显示器或打印机。所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要页面渲染引擎。 Trident页面渲染引擎 –> IE系列浏览器; Gecko页面渲染引擎 –> Mozilla Firefox; KHTML页面渲染引擎或WebKit框架 –> Safafi和Google Chrome; Presto页面渲染引擎 –> Opera Trident(又称为MSHTML),是微软的视窗操作系统(Windows)搭载的网页浏览器—Internet Explorer的页面渲染引擎的名称,它的第一个版本诞生于1997年10月Internet Explorer第四版中,IE7做了的重大的变动,除了加入新的技术之外,并增加对网页标准的支持,目前是互联网上最流行的排版引擎。 Gecko是套开放源代码的、以C++编写的页面渲染引擎。Gecko是跨平台的,能在Microsoft Windows、Linux和Mac OS X等主要操作系统上运行。它是最流行的页面渲染引擎之一,其流行程度仅次于Trident。 KHTML,是HTML页面渲染引擎之一,由KDE所开发。KHTML拥有速度快捷的优点,但对错误语法的容忍度则比Mozilla产品所使用的Gecko引擎小。苹果电脑于2002年采纳了KHTML,作为开发Safari浏览器之用。WebCore及WebKit引擎均是KHTML的衍生产品;WebKit是 Mac OS X v10.3及以上版本所包含的软件框架,WebKit是Mac OS X的Safari网页浏览器的基础。 Presto是一个由Opera Software开发的浏览器页面渲染引擎,应用于Opera 7.0~9.60版,它取代了旧版Opera中所使用的Elektra页面渲染引擎,包括加入动态功能,例如网页或其部分可随着DOM及Script语法的事件而重新排版。 Java,是一种可以撰写跨平台应用软件的面向对象的程序设计语言,Java 编程语言的风格十分接近C++语言。微软推出的.NET平台以及模仿Java的C#语言正是与之竞争下的产物。 Tasman,是微软的Internet Explorer for Mac浏览器所使用的页面渲染引擎,也是为尝试支援W3C所制定的网页标准而设计的。在Mac版的Microsoft Office 2004中,电子邮件客户端Microsoft Entourage使用的就是Tasman页面渲染引擎。 渲染实现原理 Mozilla架构设计:界面和实现分离。采用标记语言,JavaScript,C++来开发。JSEngine就是指 SpideMonkey,Layout就是指Gecko。Mozilla的一个关键部分是XPCOM和NSPR。 WebKit处理流程:
转载请注明原创地址:http://www.cnblogs.com/softlover/archive/2012/11/20/2779878.html html5为web的form表单增强了一个功能,他就是input的占位符--placeholder。占位符的作用是,当input内容为空或者没有被聚焦的时候,input显示占位符的内容。这是个很棒的功能,但不是所有的浏览器都支持。本教程将向你介绍,如何使用 Modernizr 类库去判断浏览器是否支持该属性,然后使用jquery动态显示占位符。 demo预览地址:http://webdesignerwall.com/demo/html5-placeholder-text/ demo下载地址:http://webdesignerwall.com/file/html5-placeholder.zip 以前使用JavaScript实现的方式 在没有placeholder属性的日子里,我们使用javascript去模拟他的实现。在下面的例子里,我们向input添加了一个value属性。input聚焦的时候,我们判断value的值是否是‘search’,是的话就清空内容。当input失去焦点的时候,我们判断内容是否为空,为空就将value设置为‘search’。 <input type="text" value="Search" onfocus="if (this.value == 'Search') {this.value = '';}" onblur="if (this.value == '') {this.value = 'Search';}" /> 使用jquery生成占位符 现在使用html5的placeholder,从语义上来说,他比value属性更能表达我们的意图。但是不是所有浏览器都支持该属性,所以我们需要借助 Modernizr and jQuery 来帮助我们。 Modernizr用来判断浏览器是否支持placeholder属性,如果不支持,则运行jquery语句。他会把所有包含placeholder属性的html元素找出来,并把他存在变量中。在元素获得和失去焦点的时候,脚本会判断value值和placeholder值,来决定value值最终的内容。 如果你想在自己的站点中使用这个功能,需要下载modernizr和jquery类库,并保证他们的引用地址正确。 <script src="jquery.js"></script> <script src="modernizr.js"></script> <script> $(document).ready(function(){ if(!Modernizr.input.placeholder){ $('[placeholder]').focus(function() { var input = $(this); if (input.val() == input.attr('placeholder')) { input.val(''); input.removeClass('placeholder'); } }).blur(function() { var input = $(this); if (input.val() == '' || input.val() == input.attr('placeholder')) { input.addClass('placeholder'); input.val(input.attr('placeholder')); } }).blur(); $('[placeholder]').parents('form').submit(function() { $(this).find('[placeholder]').each(function() { var input = $(this); if (input.val() == input.attr('placeholder')) { input.val(''); } }) }); } </script> 移出webkit搜索框样式 webkit浏览器为搜索框添加了额外的样式,我们需要使用下面的脚本去移出他。 input[type=search] { -webkit-appearance: none;} input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { display: none; } 好了,本节课程到此为止。 原文地址:http://webdesignerwall.com/tutorials/cross-browser-html5-placeholder-text HTML5实践系列
转载请注明原创地址:http://www.cnblogs.com/softlover/archive/2012/11/23/2780892.html 之前的教程《HTML5实践 -- css3图片样式》,我介绍了如何为图片添加background-image包装,借助box-shadow 和 border-radius为图片设置多种多样的样式,有兴趣的朋友可以回头阅读。但是最近我在设计 PhotoTouch 主题的时候遇到了问题,background-image大小不能调整,这对于响应式设计就不太理想了。今天我们将尝试解决问题。 demo预览地址:http://webdesignerwall.com/demo/css3-image-styles-part-2/ 问题 大多数浏览器对图片的border-radius 和内嵌 box-shadow效果渲染的并不是很完美。这也就意味着,你不能为图片创建浮雕、高光和压缩等效果。 之前的解决方案 在之前的解决方案中,我们为图片的包装添加background-image属性,解决了上述问题。 background-image存在的问题 使用background-image的问题是,他不能实现图片大小的动态缩放。所以,这种方式对于要求图片缩放的响应式设计来说就不那么适用了。 新解决方案 新解决方式和之前的有些相似,我们把css3的效果添加到图片遮罩层 :after 伪类上,这样做的好处是图片保持了完整性和可收缩性。 实现动态效果的jquery语句 jquery会查询#demo下面所有的图片,然后为他们动态添加 span 包装。 <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5/jquery.min.js"></script> <script type="text/javascript"> $(document).ready(function(){ $('#demo img').each(function() { var imgClass = $(this).attr('class'); $(this).wrap('<span class="image-wrap ' + imgClass + '" style="width: auto; height: auto;"/>'); $(this).removeAttr('class'); }); }); </script> 输出结果 上面的代码会输出下面的结果: <span class="image-wrap " style="width: auto; height: auto;"> <img src="image.jpg"> </span> css技巧 css技巧很简单,遮罩的效果被用在了.image-wrap:after 上面,border-radius 同时用在了图片和.image-wrap:after上面,来实现样式效果。 css .image-wrap { position: relative; display: inline-block; max-width: 100%; vertical-align: bottom; } .image-wrap:after { content: ' '; width: 100%; height: 100%; position: absolute; top: -1px; left: -1px; border: solid 1px #1b1b1b; -wekbit-box-shadow: inset 0 0 1px rgba(255,255,255,.4), inset 0 1px 0 rgba(255,255,255,.4), 0 1px 2px rgba(0,0,0,.3); -moz-box-shadow: inset 0 0 1px rgba(255,255,255,.4), inset 0 1px 0 rgba(255,255,255,.4), 0 1px 2px rgba(0,0,0,.3); box-shadow: inset 0 0 1px rgba(255,255,255,.4), inset 0 1px 0 rgba(255,255,255,.4), 0 1px 2px rgba(0,0,0,.3); -webkit-border-radius: 7px; -moz-border-radius: 7px; border-radius: 7px; } .image-wrap img { vertical-align: bottom; -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.4); -moz-box-shadow: 0 1px 2px rgba(0,0,0,.4); box-shadow: 0 1px 2px rgba(0,0,0,.4); -webkit-border-radius: 6px; -moz-border-radius: 6px; border-radius: 6px; } 图片样式 很多不同的效果,例如:浮雕、抠图、压缩和高光等,都可以通过使用多个内嵌box-shadows属性来实现。当然你也可以通过使用:before,来实现另外的布局效果,例如高光。可以查看demo源代码,去了解更多详情。之后,可以调整你浏览器窗口的大小,来查看图片大小是否发生了变化。 浏览器兼容 这个技巧,只要是支持 Javascript 和 CSS3 的大多数现代浏览器都支持,例如:Chrome, Firefox, Safari, and IE9+。 原文地址:http://webdesignerwall.com/tutorials/css3-image-styles-part-2 HTML5实践系列
转载请注明原创地址:http://www.cnblogs.com/softlover/archive/2012/11/22/2779876.html 在css3中,直接在图片上使用box-shadow 和 border-radius,浏览器并不能很好的渲染。但是如果把图片作为background-image,添加的样式浏览器可以很好的渲染。我将会介绍如何使用box-shadow, border-radius 和 transition创建不同图片样式效果。 demo预览地址:http://webdesignerwall.com/demo/css3-image-styles/ 问题 通过查看demo能注意到,我们为第一行图片设置了border-radius 和 内嵌box-shadow。firefox渲染了图片的border-radius,但是没有渲染内嵌box-shadow。chrome和Safari两种效果都没有渲染。 .normal img { border: solid 5px #000; -webkit-border-radius: 20px; -moz-border-radius: 20px; border-radius: 20px; -webkit-box-shadow: inset 0 1px 5px rgba(0,0,0,.5); -moz-box-shadow: inset 0 1px 5px rgba(0,0,0,.5); box-shadow: inset 0 1px 5px rgba(0,0,0,.5); } firefox效果: chrome/safari 变通方案 为了使border-radius 和 内嵌box-shadow能够正常工作,我们需要把图片转换成background-image的方式。 动态方式 为了动态完成这一工作,我们需要借助jquery为每一个图片添加背景图片的包装。下面的js代码为每一个图片添加了一个span的包装,span的背景图片路径就是图片的路径。 代码比较简单,我想就没有讲解的必要了。不清楚了可以直接去查jquery的api。 <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5/jquery.min.js"></script> <script type="text/javascript"> $(document).ready(function(){ $("img").load(function() { $(this).wrap(function(){ return '<span class="image-wrap ' + $(this).attr('class') + '" style="position:relative; display:inline-block; background:url(' + $(this).attr('src') + ') no-repeat center center; width: ' + $(this).width() + 'px; height: ' + $(this).height() + 'px;" />'; }); $(this).css("opacity","0"); }); }); </script> 输出 上面的代码会输出如下结果: <span class="image-wrap " style="position:relative; display:inline-block; background:url(image.jpg) no-repeat center center; width: 150px; height: 150px;"> <img src="image.jpg" style="opacity: 0;"> </span> 圆形图片 添加我们使用border-radius来实现圆形图片的效果,效果如下: css: .circle .image-wrap { -webkit-border-radius: 50em; -moz-border-radius: 50em; border-radius: 50em; } 卡片风格 下面是卡片风格的图片,使用了多个内嵌box-shadow。 css: .card .image-wrap { -webkit-box-shadow: inset 0 0 1px rgba(0,0,0,.8), inset 0 2px 0 rgba(255,255,255,.5), inset 0 -1px 0 rgba(0,0,0,.4); -moz-box-shadow: inset 0 0 1px rgba(0,0,0,.8), inset 0 2px 0 rgba(255,255,255,.5), inset 0 -1px 0 rgba(0,0,0,.4); box-shadow: inset 0 0 1px rgba(0,0,0,.8), inset 0 2px 0 rgba(255,255,255,.5), inset 0 -1px 0 rgba(0,0,0,.4); -webkit-border-radius: 20px; -moz-border-radius: 20px; border-radius: 20px; } 浮雕风格 下面是浮雕效果。 css: .embossed .image-wrap { -webkit-box-shadow: inset 0 0 2px rgba(0,0,0,.8), inset 0 2px 0 rgba(255,255,255,.5), inset 0 -7px 0 rgba(0,0,0,.6), inset 0 -9px 0 rgba(255,255,255,.3); -moz-box-shadow: inset 0 0 2px rgba(0,0,0,.8), inset 0 2px 0 rgba(255,255,255,.5), inset 0 -7px 0 rgba(0,0,0,.6), inset 0 -9px 0 rgba(255,255,255,.3); box-shadow: inset 0 0 2px rgba(0,0,0,.8), inset 0 2px 0 rgba(255,255,255,.5), inset 0 -7px 0 rgba(0,0,0,.6), inset 0 -9px 0 rgba(255,255,255,.3); -webkit-border-radius: 20px; -moz-border-radius: 20px; border-radius: 20px; } 柔性浮雕风格 相对于浮雕样式,新样式添加了1px blur属性。 css: .soft-embossed .image-wrap { -webkit-box-shadow: inset 0 0 4px rgba(0,0,0,1), inset 0 2px 1px rgba(255,255,255,.5), inset 0 -9px 2px rgba(0,0,0,.6), inset 0 -12px 2px rgba(255,255,255,.3); -moz-box-shadow: inset 0 0 4px rgba(0,0,0,1), inset 0 2px 1px rgba(255,255,255,.5), inset 0 -9px 2px rgba(0,0,0,.6), inset 0 -12px 2px rgba(255,255,255,.3); box-shadow: inset 0 0 4px rgba(0,0,0,1), inset 0 2px 1px rgba(255,255,255,.5), inset 0 -9px 2px rgba(0,0,0,.6), inset 0 -12px 2px rgba(255,255,255,.3); -webkit-border-radius: 20px; -moz-border-radius: 20px; border-radius: 20px; } 抠图风格 使用内嵌box-shadow就可以实现抠图效果。 css: .cut-out .image-wrap { -webkit-box-shadow: 0 1px 0 rgba(255,255,255,.2), inset 0 4px 5px rgba(0,0,0,.6), inset 0 1px 0 rgba(0,0,0,.6); -moz-box-shadow: 0 1px 0 rgba(255,255,255,.2), inset 0 4px 5px rgba(0,0,0,.6), inset 0 1px 0 rgba(0,0,0,.6); box-shadow: 0 1px 0 rgba(255,255,255,.2), inset 0 4px 5px rgba(0,0,0,.6), inset 0 1px 0 rgba(0,0,0,.6); -webkit-border-radius: 20px; -moz-border-radius: 20px; border-radius: 20px; } 变形和发光 在这个例子中我们为图片包装添加transition属性,鼠标滑过的时候,他会从圆角变为圆形。然后我们使用多个box-shadow实现发光效果。 css: .morphing-glowing .image-wrap { -webkit-transition: 1s; -moz-transition: 1s; transition: 1s; -webkit-border-radius: 20px; -moz-border-radius: 20px; border-radius: 20px; } .morphing-glowing .image-wrap:hover { -webkit-box-shadow: 0 0 20px rgba(255,255,255,.6), inset 0 0 20px rgba(255,255,255,1); -moz-box-shadow: 0 0 20px rgba(255,255,255,.6), inset 0 0 20px rgba(255,255,255,1); box-shadow: 0 0 20px rgba(255,255,255,.6), inset 0 0 20px rgba(255,255,255,1); -webkit-border-radius: 60em; -moz-border-radius: 60em; border-radius: 60em; } 高光效果 高光的效果是通过为元素添加 :after 伪类实现的。 css: .glossy .image-wrap { -webkit-box-shadow: inset 0 -1px 0 rgba(0,0,0,.5); -moz-box-shadow: inset 0 -1px 0 rgba(0,0,0,.5); box-shadow: inset 0 -1px 0 rgba(0,0,0,.5); -webkit-border-radius: 20px; -moz-border-radius: 20px; border-radius: 20px; } .glossy .image-wrap:after { position: absolute; content: ' '; width: 100%; height: 50%; top: 0; left: 0; -webkit-border-radius: 20px; -moz-border-radius: 20px; border-radius: 20px; background: -moz-linear-gradient(top, rgba(255,255,255,0.7) 0%, rgba(255,255,255,.1) 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,0.7)), color-stop(100%,rgba(255,255,255,.1))); background: linear-gradient(top, rgba(255,255,255,0.7) 0%,rgba(255,255,255,.1) 100%); } 倒影效果 在这个例子中,我们将高光效果移到底部就实现倒影效果。 css: .reflection .image-wrap:after { position: absolute; content: ' '; width: 100%; height: 30px; bottom: -31px; left: 0; -webkit-border-top-left-radius: 20px; -webkit-border-top-right-radius: 20px; -moz-border-radius-topleft: 20px; -moz-border-radius-topright: 20px; border-top-left-radius: 20px; border-top-right-radius: 20px; background: -moz-linear-gradient(top, rgba(0,0,0,.3) 0%, rgba(255,255,255,0) 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(0,0,0,.3)), color-stop(100%,rgba(255,255,255,0))); background: linear-gradient(top, rgba(0,0,0,.3) 0%,rgba(255,255,255,0) 100%); } .reflection .image-wrap:hover { position: relative; top: -8px; } 高光和倒影 本例我们使用:before 和 :after 将高光和倒影效果组合起来。 css: .glossy-reflection .image-wrap { -webkit-box-shadow: inset 0 -1px 0 rgba(0,0,0,.5), inset 0 1px 0 rgba(255,255,255,.6); -moz-box-shadow: inset 0 -1px 0 rgba(0,0,0,.5), inset 0 1px 0 rgba(255,255,255,.6); box-shadow: inset 0 -1px 0 rgba(0,0,0,.5), inset 0 1px 0 rgba(255,255,255,.6); -webkit-transition: 1s; -moz-transition: 1s; transition: 1s; -webkit-border-radius: 20px; -moz-border-radius: 20px; border-radius: 20px; } .glossy-reflection .image-wrap:before { position: absolute; content: ' '; width: 100%; height: 50%; top: 0; left: 0; -webkit-border-radius: 20px; -moz-border-radius: 20px; border-radius: 20px; background: -moz-linear-gradient(top, rgba(255,255,255,0.7) 0%, rgba(255,255,255,.1) 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,0.7)), color-stop(100%,rgba(255,255,255,.1))); background: linear-gradient(top, rgba(255,255,255,0.7) 0%,rgba(255,255,255,.1) 100%); } .glossy-reflection .image-wrap:after { position: absolute; content: ' '; width: 100%; height: 30px; bottom: -31px; left: 0; -webkit-border-top-left-radius: 20px; -webkit-border-top-right-radius: 20px; -moz-border-radius-topleft: 20px; -moz-border-radius-topright: 20px; border-top-left-radius: 20px; border-top-right-radius: 20px; background: -moz-linear-gradient(top, rgba(230,230,230,.3) 0%, rgba(230,230,230,0) 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(230,230,230,.3)), color-stop(100%,rgba(230,230,230,0))); background: linear-gradient(top, rgba(230,230,230,.3) 0%,rgba(230,230,230,0) 100%); } 胶带风格 在这个例子中,我们使用:after来实现胶带的效果。 css: .tape .image-wrap { -webkit-box-shadow: inset 0 0 2px rgba(0,0,0,.7), inset 0 2px 0 rgba(255,255,255,.3), inset 0 -1px 0 rgba(0,0,0,.5), 0 1px 3px rgba(0,0,0,.4); -moz-box-shadow: inset 0 0 2px rgba(0,0,0,.7), inset 0 2px 0 rgba(255,255,255,.3), inset 0 -1px 0 rgba(0,0,0,.5), 0 1px 3px rgba(0,0,0,.4); box-shadow: inset 0 0 2px rgba(0,0,0,.7), inset 0 2px 0 rgba(255,255,255,.3), inset 0 -1px 0 rgba(0,0,0,.5), 0 1px 3px rgba(0,0,0,.4); } .tape .image-wrap:after { position: absolute; content: ' '; width: 60px; height: 25px; top: -10px; left: 50%; margin-left: -30px; border: solid 1px rgba(137,130,48,.2); background: -moz-linear-gradient(top, rgba(254,243,127,.6) 0%, rgba(240,224,54,.6) 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(254,243,127,.6)), color-stop(100%,rgba(240,224,54,.6))); background: linear-gradient(top, rgba(254,243,127,.6) 0%,rgba(240,224,54,.6) 100%); -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.3), 0 1px 0 rgba(0,0,0,.2); } 变形和着色 在这个例子中,我们在元素上使用:after,当鼠标进过的时候实现径向渐变的效果。 css: .morphing-tinting .image-wrap { position: relative; -webkit-transition: 1s; -moz-transition: 1s; transition: 1s; -webkit-border-radius: 20px; -moz-border-radius: 20px; border-radius: 20px; } .morphing-tinting .image-wrap:hover { -webkit-border-radius: 30em; -moz-border-radius: 30em; border-radius: 30em; } .morphing-tinting .image-wrap:after { position: absolute; content: ' '; width: 100%; height: 100%; top: 0; left: 0; -webkit-transition: 1s; -moz-transition: 1s; transition: 1s; -webkit-border-radius: 30em; -moz-border-radius: 30em; border-radius: 30em; } .morphing-tinting .image-wrap:hover:after { background: -webkit-gradient(radial, 50% 50%, 40, 50% 50%, 80, from(rgba(0,0,0,0)), to(rgba(0,0,0,1))); background: -moz-radial-gradient(50% 50%, circle, rgba(0,0,0,0) 40px, rgba(0,0,0,1) 80px); } 羽化边缘圆形 我们同样可以使用径向渐变产生遮罩,实现羽化的效果。 css: .feather .image-wrap { position: relative; -webkit-border-radius: 30em; -moz-border-radius: 30em; border-radius: 30em; } .feather .image-wrap:after { position: absolute; content: ' '; width: 100%; height: 100%; top: 0; left: 0; background: -webkit-gradient(radial, 50% 50%, 50, 50% 50%, 70, from(rgba(255,255,255,0)), to(rgba(255,255,255,1))); background: -moz-radial-gradient(50% 50%, circle, rgba(255,255,255,0) 50px, rgba(255,255,255,1) 70px); } 浏览器兼容性 这种实现方式在大多数支持border-radius, box-shadow, :before and :after特性的浏览器中(例如Chrome, Firefox 和 Safari),都能很好的工作。在不支持新特性的浏览器中,只会显示原始图片。 创造你自己的实现 借助:before 和:after伪类能为图片创造很多种样式,你可以自己尝试创建出新的效果。 原文地址:http://webdesignerwall.com/tutorials/css3-image-styles HTML5实践系列
转载请注明原创地址:http://www.cnblogs.com/softlover/archive/2012/11/21/2779873.html 今天我们的内容是css3的text-shadow、box-shadow 和 border-radius几个属性的介绍,他能增强页面布局,值得学习。 RGBA 前三个值分别代码RBG的值,最后一个值代表透明度(0表示透明,1表示不透明)。 RGBA可以用于任何和color有关的属性,例如字体颜色、边框颜色、背景颜色和阴影颜色等。 文字阴影 文字阴影的结构按照这样的顺序:x-offset, y-offset, blur, 和 color。 为x-offset设置负值会将阴影位置改变到左边,为y-offset设置负值会将阴影位置改变到头部。我们也可以使用RBGA设置阴影的颜色。 你可以设置一组text-shadow,中间以逗号相隔。下面的例子使用两个text-shadow(顶部1px 和 底部1px),为名字设置了新闻文字效果。 text-shadow: 0 1px 0 #fff, 0 -1px 0 #000; 边框半径 边框半径对属性赋值的便捷写法类似于padding和margin(例如:border-radius: 20px)。为了让有些浏览器能正确渲染效果,需要在属性前面加前缀,例如针对webkit浏览器需要添加 "-webkit-" 前缀,firefox浏览器需要添加 "-moz-" 前缀。 你可以为不同的边角设置不同的半径,注意webkit和firefox浏览器,每个边角有不同的属性名称。 盒子阴影 盒子阴影的结构和text-shadow 属性一样,按照这样的顺序: x-offset, y-offset, blur, 和 color。 你可以为盒子阴影设置很多效果,例如下面的例子使用一组参数来设置效果(参数之间以逗号相隔)。 -moz-box-shadow: -2px -2px 0 #fff, 2px 2px 0 #bb9595, 2px 4px 15px rgba(0, 0, 0, .3); 原文地址:http://webdesignerwall.com/tutorials/the-basics-of-css3 HTML5实践系列
转载请注明原创地址:http://www.cnblogs.com/softlover/archive/2012/11/20/2779845.html 今天我将介绍如何使用css制作一个可伸缩的mobile的搜索框,他非常适合于mobile的响应式设计的需要。本教程没有使用JavaScript,只使用了原生css属性,所以这是一个非常简单而高效的实现。 demo预览地址:http://webdesignerwall.com/demo/expandable-search-form/ 目的 在移动设备上显示信息,要做到寸土寸金,珍惜每一寸的屏幕。例如搜索框的设计,在一般情况下处于收缩状态,激活的时候才将他扩展开来,这样做可以为屏幕上其他元素提供更多地显示区域。这就是本课程要完成的工作。我们先来看一个效果图: 在我的站点 Best Web Gallery 也有类似的设计,当查询按钮被点击的时候,触发jquery的focus事件用来渐入搜索框。 HTML代码 下面的代码使用了html5的search标签: <form> <input type="search" placeholder="Search"> </form> 重新设置webkit默认搜索输入框 默认的webkit输入框的风格如下: 为了移除默认效果,让他看起来更像一般的文字输入框,我们需要添加如下css: input[type=search] { -webkit-appearance: textfield; -webkit-box-sizing: content-box; font-family: inherit; font-size: 100%; } input::-webkit-search-decoration, input::-webkit-search-cancel-button { display: none; } 设置输入框样式 我不会逐行讲解每条css语句,这里只强调几点。我默认设置的search的宽度是55px,当他在focus状态下会扩展,宽度变为130px。transition属性实现动画效果,Box-shadow用来在输入框上实现发光的效果。 例子B 在demo B 中,搜索框被最小化了,只存在一个查询的icon而没有文字输入部分。我改变了search的padding和width属性,来显示一个完美的圆形的按钮。我还使用color:transparent来隐藏文本区域。 浏览器兼容 他在所有主流的浏览器上运行正常,例如:Chrome, Firefox, Safari, and IE8+。但不支持ie7及更低版本浏览器,因为ie不能识别 search 输入框,并且也不支持:focus伪类。 原文地址:http://webdesignerwall.com/tutorials/expandable-mobile-search-form HTML5实践系列
桌面网站的一些设计原则是大家广泛认可,并积极付诸实践的。例如:系统状态保持可见,避免错误信息,在错误发生时,应该提供一些具体的指南帮助用户解决这些问题等。 很多人认为这些适合网页设计的原则和指南也同样适用于移动平台。毕竟,网页设计是从一些基础的,基于文本的HTML出发,才发展成为今天的WEB标准的。因此,我们也可以想象依靠这些原则,手机站点的设计也会和网页站点的设计一样,获得巨大的成功。 然而,移动站点的设计仍处于初步阶段。Jakob Nielsen在2009年移动可用性调查时指出,相比与网页站点80%的成功率,用户使用移动设备查看移动站点时平均成功率只有64%, 形式要素的差 异对用户交互成功率的影响是巨大的,因此,在进行移动站点的设计时要充分考虑移动设备的形式要素, 随着手机站点设计的持续增长,一些新的原则,以及一些好的设计实践将会浮出水面。作为前进的第一步,本人通过分析一些成功的手机站点来研究两者的不同。在我的研究中,涵盖了航空,电子商务,社交网站,娱乐等一些较成功的站点,并得出了至少10个区别。 1. 内容优先 桌面站点可以有1024*768的分辨率,而智能机仅有320*480分辨率,如何在如此小的分辨率中,在不降低用户体验效果前提下进行设计是很有 挑战性的。桌面站点常常包涵更广的内容,而移动站点仅包涵一些符合使用情境的主要功能和特征,如图1、图2所示Orbitz的桌面站点和移动站点。移动站 点应该通过移动设备将用户最需要的内容和特征展现给用户。一些站点内容,信息架构和屏幕布局都是在深度理解客户需求的前提下设计的。 图 1—Orbitz桌面站点特征 图 2—Orbitz移动站点特征 2. 垂直浏览取代水平浏览 如图三Urban Outfitters站点所示的那样,在呈现数据结构和数据内容时,水平导航是一种广泛采用的导航方式。用户可以从左到右,点击不同的站点链接来浏览。Joshua Porter在一篇名为“The Challenge of Moving to Horizontal Navigation“的 博客中,讨论了在页面顶端而不是页面两侧使用水平导航的好处。至于顶端时,用户可以更容易聚焦于页面的内容,而在两侧时,会很容易干扰用户视线。在我的研 究中,90%的移动站点采用垂直导航的方式取代水平导航,包括如下图所示的Urban Outfitters移动站点。 图 3—Urban Outfitters桌面站点中的水平导航 图 4—Urban Outfitters移动站点的垂直导航 3. 导航条,标签和超文本 如图5所示,超链接是因特网站点的主要组成部分,然而,在移动站点中,我们则很少看到超链接。并不是在移动站点中没有超链接,而是被一些导航条,标签和按钮等取代了,如图6所示。用户使用手指来操作移动设备是产生垂直导航方式的原因之一。 在电脑上,移动鼠标,点击超链接是很理想的查看信息的方式,但是在移动设备中,通过手指触摸屏幕来打开超链接就不那么容易了。用户可以很容易激活一 个链接,进入一个新页面,但,这并不是用户期望的页面,如果这样的话,会产生非常差的体验效果。Fitts定律告诉我们使用指点设备达到一个目标的时间与 设备当前的位置和目标位置的距离,以及目标的大小有移动的关系。在一些大的手机站点中,导航条,标签和按钮会吸引更多的注意力。 图 5—Kayak 网页链接 图 6—Kayak 手机页,没有链接 4. Text and Graphics文本和图形 在网页中,我们经常会看到一些促销,营销或者导航的图形信息。如图7所示的dell站点,设计者经常需要设计一些促销或营销的图形,如图8所示、公 司LOGO始终保持着导航的目标,通过它,用户可以进入主页。而在移动站点设计中,应该减少这些图形,原因有二:其一,一些移动设备并不支持和传统网页站 点一样显示方式;其二,移动屏幕较小,显示内容有限,除此之外,过多的显示内容会降低移动设计的运行速度。 图 7—Dell首页,有图形 图 8—Dell 移动页,只有较少的图形 5. 全局导航与情境导航 桌面站点提出使用多种导航方式,如图9 Best Buy 站点。一些全局性的导航可以保持站点的一致性,而其他一些情境导航会随着用户使用站点的不同而不同。在移动站点中,全局导航是最常见的导航方式,如图10 Best buy的移动站点就是非常典型的例子。 在移动站点中,移动设备有限的屏幕决定了应该减少全局导航和情境导航。然而,缺少全局导航和情境导航则会让用户迷失,为此,在构建移动内容时,应该尽量减少层级关系,这样,用户无需挖得太深就可以找到自己所需的信息,作为设计师,应该让用户在迷失之前找到自己的信息。 图 9—在Best Buy桌面站点中各式各样的情境导航 图 10—在Best Buy 移动站点中,没有情境导航 6. 页脚 在桌面站点设计中,有两种典型的页脚,第一种页脚提供了一些内容的连接,用户可以查看主页,或其他一些较低优先级的内容,如“人才招聘”和“站点地图”。第二种脚注,则提供了用户想看的所有内容结构,如图11所示,在页脚中列出一些快速入口,用户就可以纵观整个站点。在移动站点中,通过页脚,用户可以查看首页,但尽量保持最少连接数,如图12所示,在页脚中,不包含全部快速链接。 图 11—Dell 桌面站点的页脚 图 12—Dell 移动站点,较少的页脚 7. 面包屑 如图13所示,在桌 面站点中,面包屑导航可以有效地标明用户所在的位置,查看自己的导航路径,但通常会让用户产生站点内容多,层级关系深的感觉。面包屑导航方式很少出现在移 动站点中,通常也是没有必要的。有限的空间结构是原因之一,另外,这种方式使得用户需要经过很深的途径才能找到所需要的信息。同样,我们应该让用户在有迷 失感之前获取想要的信息。 图 13—Amazon移动站点的面包屑 8. 进度条 在桌面站点中,如果用户需要通过多种步骤才能完成某一过程,如购买过程或者填写较长注册表过程,如表14所示, 在页面的顶端经常会给出一个进度条,指导用户完成这个过程,这种进度条在移动站点中还没有出现。 采用一些替代的方法,让用户无需进度条,就可以表明当前所在的位置。如,不使用一些暗含用户操作“下一步”或“继续”的按钮,使用明确的标签按钮,告知用户的下一个步骤,如“前往收银台”“指定送货及付款”。这样,用户不仅知道当前的过程,还期待下一步的信息。 图 14—Amazon 站点的进度条 9. 集成手机功能 智能机是通信设备,打电话是其基本功能。尽管移动平台的设计和内容都是有限的,但仍具有一些桌面平台无法比拟的新机会,例如,可以使用直接拨打电话 或短信的方式订购物品,如图15所示,将促销短信与产品功能进行整合,用户只需选择一个手机号码,然后通过这个号码来打电话或发短信,无需输入数字。 图 15— Best Buy 移动站点,电话购物 10. 本地化和个性化搜索 基于地理位置的服务是移动站点独一无二的优势。仅在5年前,地理位置服务才和消费市场结合起来,现在,在一些移动程序和网页站点中,地理位置服务作为增值服务的一部分而广泛使用。 很多移动设备可以自动检测用户的地点,并给出一些本地化的检索结果。如图16,Best Buy本地商店搜索功能,Yelp的餐厅搜索,Kayak的班机搜索,通过了解用户的交易地点以及一些临时服务,商家就可以有针对性地推广自己的产品或服务。 图 16—在Kayak中,自动监测地理位置并给出一些检索建议 小结 基于我们的研究,我们总结了移动站点和桌面站点设计10大不同点: 桌面站点可以包含更丰富的信息,而移动站点包含一些在大部分时间,地点中使用的典型功能和特征。 桌面站点采用顶端的水平导航方式来呈现站点结构和内容,而90%的移动站点采用垂直导航的方式。 桌面站点通常使用超链接,移动站点极少使用超链接 桌面站点会根据促销,营销,导航目的的不同设计不同的图形,移动站点应避免一些促销或营销的图形,较少使用导航图形。 在桌面站点中,可以使用各种各样的导航方式,如全局导航和情境导航。移动站点采用全局导航,情境导航极少。 在桌面站点中,用户可以通过页脚查看站点内容,或查看一些快速链接。移动站点较少采用一些页脚,更不会使用页脚来包含一些快速链接。 在桌面站点中,面包屑导航可以帮助用户找到需要的页面,或者返回查看一些导航路径。而在移动站点设计中,由于自身的一些平台结构的限制,面包屑导航的方式是没有必要的。 桌面站点通常在页面顶端增加进度条来引导用户完成某项进程。移动站点并不出现进度条。 移动站点可以更好地整合手机功能,如电话直接订货或发送促销信息。 移动站点可以使用一些技术手段自动检测获取本地搜索结果。根据用户的一些喜好提供个性化的搜索结果,对用户来说更加重要 原文地址:http://www.iptu.net/index.php/archives/3059.iptu
上一篇我们谈到了移动网站中的标签,想必很多人也很想了解Mobile CSS的情况吧,本文将和大家一起探讨移动网站中的CSS标准。 介绍 Mobile css的标准也是有些复杂的,与前一篇文章中提到的类似,之前存在着一个W3C制定的CSS Mobile Profile 1.0以及OMA的WAP CSS 1.0,事实上它们都是CSS 2.1的子集,而且内容非常接近,不同的是,WAP CSS 1.0针对移动设备加入了一些扩展,这些扩展通过-wap-前缀来实现。 后来,W3C将二者进行了整合,吸收了WAP CSS1.0的一些优点,推出了CSS Mobile Prifile 2.0规范,它也是CSS 2.1的一个子集。我们本文将主要讨论这个规范。 CSS Mobile Profile 2.0中的CSS支持 因为这是CSS 2.1的一个子集,那么我们对这个规范的内容应该不会陌生,我们通过这个表格可以很直观的看到CSS MP对CSS的支持情况: 支持的 不支持的 选择器 全局选择器(*)、类型选择器(比如h1, div, p等)、子选择器(p>span)、链接伪类 (:link,:visited)、动态伪类(:active, :focus)、类选择器、id选择器、分组(h1,h2,h3{}…) :first-child、:hover 、:lang() 伪类, :first-letter 、:first-line 伪元素, 兄弟选择器(比如h1 + p),属性选择器 (比如 a[href], a[target="_blank"]) 背景和边框 background, background-color, background-image, background-repeat, background-attachment, background-position, border, border-width, border-color, border-style(注1) 无 定位 position, top, right, bottom, left, z-index 无 列表 list-style, list-style-image, list-style-type list-style-position 基本的盒模型 display(注2), margin, padding, height, min-height, max-height, width, min-width, max-width, float, clear, visibility, overflow(注3), overflow-style(注4) 无 色彩 color 无 字体 font, font-family, font-style, font-variant, font-weight, font-size(注5) 无 文字 text-indent, text-align, text-decoration(注6), text-transform, white-space word-spacing, letter-spacing, unicode-bidi 线形 vertical-align(注7) line-height 基本的用户界面 utline, outline-color, outline-style, outline-width cursor 滚动 marquee-style, marquee-direction, marquee-play-count, marquee-speed 无 @规则 @charset, @import, @media(注8), @namespace @page 注1:border-style只支持常用的none、dotted、dashed、solid和inherit,其它的几个并没有被列入规范。 注2:display仅限于inline、block、list-item、none和inherit,不支持run-in和inline-block 注3:overflow只支持auto 注4:overflow-sytle只支持marquee值 注5:font-size只支持大小关键词,比如small、medium、bigger等,px、pt和百分比等不被支持。 注6:text-decoration只支持none、blink、underline和inherit等,overline、line-trough不被支持。 注7:vertical-align 只支持top, middle, bottom, baseline 和inherit。sub, super, text-top, text-bottom, 百分比和长度不被支持 注8:@media规则只支持 handheld 和all 媒体类型。 用法与浏览器支持 就像之前文章里面提到的那样,目前绝大多数的手机是支持XHTML Basic 1.0和XHTML MP 1.0标准的,这就意味着在某种程度上移动网站的HTML/XHTML代码是可以与桌面版的相兼容甚至完全一致的。甚至有些网站的移动版直接使用HTML 4/5或者XHTML 1.0的DTD。这样移动版网站可以直接通过handheld的media type来制定一个移动页面专用的CSS文件: 1 <link rel="stylesheet" media="handheld" href=""> 绝大多数传统手机上的浏览器都支持handheld媒体类型,包括opera mini和windows mobile中的IE。 其实考虑到传统手机的自身的限制和移动网络速度的限制,通过media type来加载外部样式是不可取的,通常用于移动版页面的样式不多,而多加载一个外部样式的代价是巨大的。所以,大部分的网站的移动版采用在head中嵌入样式表的方式。 如果,你一定要采用外部样式的话,最好不要用@import,因为有些手机浏览器并不支持。 特别值得一提的是,mobile webkit(包括iphone safari和android chrome lite等)不支持handheld。 而在视觉上的差异,主要是字体的表现差异,这和各个手机浏览器有关,想要做的像素完美,不是件容易的事情。 总结 虽然,移动网站貌似不用考虑太多的功能,布局简单,功能简单,但是现实并没有想象中的那么简单。移动网站面临的最大的问题是浏览器众多,手机终端差别又很大,开发的时候,会遇到这种细节问题。 目前国内移动网站的前端开发,还处于起步阶段,随着iPhone和Android等智能手机的流行,针对高端智能手机设备的网站开发将逐渐盛行,如 @SMbey0nd 所言,Mobile Web风暴,即将席卷中国,对于这个相对较新的领域,我们还有很多事情要做。 其实本文仅仅涉及到Mobile CSS的一些非常基础的方面,希望可以抛砖引玉,引起更多的人研究和分享移动网站开发的前端技术和技巧,如果你有较深入的研究,欢迎通过前端观察与大家分享。 参考 http://www.w3.org/TR/css-mobile/ Mobile style – CSS Mobile Profile 2.0 complete css guide Mobile profile 原文地址:http://www.iptu.net/index.php/archives/2726.iptu
在 此所说的移动平台前端开发是指针对高端智能手机(如Iphone、Android)做站点适配也就是WebApp,并非是针对普通手机开发Wap 2.0,所以在阅读本篇文章以前,你需要对webkit内核的浏览器有一定的了解,你需要对HTML5和CSS3有一定的了解。如果你已经对此有所了解, 那现在就开始往下阅读吧…… 1、首先我们来看看webkit内核中的一些私有的meta标签,这些meta标签在开发webapp时起到非常重要的作用 <meta content=”width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;” name=”viewport” /> <meta content=”yes” name=”apple-mobile-web-app-capable” /> <meta content=”black” name=”apple-mobile-web-app-status-bar-style” /> <meta content=”telephone=no” name=”format-detection” /> 第一个meta标签表示:强制让文档的宽度与设备的宽度保持1:1,并且文档最大的宽度比例是1.0,且不允许用户点击屏幕放大浏览; 第二个meta标签是iphone设备中的safari私有meta标签,它表示:允许全屏模式浏览; 第三个meta标签也是iphone的私有标签,它指定的iphone中safari顶端的状态条的样式; 第四个meta标签表示:告诉设备忽略将页面中的数字识别为电话号码 2、HTML5标签的使用 在开始编写webapp时,哥建议前端工程师使用HTML5,而放弃HTML4,因为HTML5可以实现一些HTML4中无法实现的丰富的WEB应 用程序 的体验,可以减少开发者很多的工作量,当然了你决定使用HTML5前,一定要对此非常熟悉,要知道HTML5的新标签的作用。比如定义一块内容或文章区域 可使用section标签,定义导航条或选项卡可以直接使用nav标签等等。 3、放弃CSS float属性 在项目开发过程中可以会遇到内容排列排列显示的布局(见下图),假如你遇见这样的视觉稿,哥建议你放弃float,可以直接使用display:block; 4、利用CSS3边框背景属性 看看这样一个按钮 这个按钮有圆角效果,有内发光效果还有高光效果,这样的按钮使用CSS3写是无法写出来的,当然圆角可以使用CSS3来写,但高光和内发光却无法使用CSS3编写, 这个时候你不妨使用-webkit-border-image来定义这个按钮的样式。 -webkit-border-image就个很复杂的样式属性,你一开始可以无法快速理解,建议你阅读一篇关于border-image的文章 5、块级化a标签 请保证将每条数据都放在一个a标签中,为何这样做?因为在触控手机上,为提升用户体验,尽可能的保证用户的可点击区域较大。 6、自适应布局模式 在编写CSS时,我不建议前端工程师把容器(不管是外层容器还是内层)的宽度定死。为达到适配各种手持设备,我建议前端工程师使用自适应布局模式 (支付宝 采用了自适应布局模式),因为这样做可以让你的页面在ipad、itouch、ipod、iphone、android、web safarik、chrome都能够正常的显示,你无需再次考虑设备的分辨率 。 原文地址:http://www.iptu.net/index.php/archives/2788.iptu
很多网站都通过User-Agent来判断浏览器类型,如果是3G手机,显示手机页面内容,如果是普通浏览器,显示普通网页内容。 谷歌Chrome浏览器,可以很方便地用来当3G手机模拟器。在Windows的【开始】–>【运行】中输入以下命令,启动谷歌浏览器,即可模拟相应手机的浏览器去访问3G手机网页: 谷歌Android: chrome.exe --user-agent="Mozilla/5.0 (Linux; U; Android 2.2; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1" 苹果iPhone: chrome.exe --user-agent="Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_1 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8B117 Safari/6531.22.7" 诺基亚N97: chrome.exe --user-agent="Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/20.0.019; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.18124" 试一试,分别用Android、iPhone、诺基亚访问http://www.163.com/、http://blog.s135.com/、http://www.google.com.hk/、http://3g.qq.com、http://t.sina.cn这些3G手机网页,看看有什么不同。 更多款手机的User-Agent:http://www.zytrax.com/tech/web/mobile_ids.html 备注:如果想切换回普通浏览器模式,关掉所有Chrome浏览器,重开即可。如果不想关闭浏览器,切回普通浏览器模式,则访问: chrome.exe --user-agent="Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3" 原文地址:http://www.iptu.net/index.php/archives/2786.iptu
iPhone 和 iPad 等苹果设备使用主屏幕 (Home Screen, 也称桌面) 管理应用程序, 还可以通过浏览器的添加到主屏幕功能将网站链接作为快捷方式添加为主屏幕图标. 是否你也想过为网站定义一个图标, 如果用户将网站添加至主屏幕, 网站链接看起来更像原生程序, 也能获得更多的关注. 除了兼容手机端的浏览体验, 我们还能做得更多, 为 iPhone 和 iPad 自定义网站的主屏幕图标也算其中之一, 本文将对网站的主屏幕图标及其设定方法为进行详细介绍. 网站的主屏幕图标 iPhone/iPad 上的原生浏览器可以向主屏幕添加小图标, 作为网站入口. 如下: 如果网站没做过兼容处理, 小图标显示的是网页截图. 效果如下: 我们再看看 Apple 自己的页面, 它已经设定了主屏幕图标, 效果如下: 设定主屏幕图标 看到上面的截图, 我想你会打算自定义一个图标了. iOS 的网页图标与传统的网页 favicon 相似, 处理方式也差不多, 下面会为你介绍几种处理方式. 放置在默认位置 创建一个 PNG 图片, 命名为 apple-touch-icon.png 或者 apple-touch-icon-precomposed.png, 放置在网站根目录即可. 指定图标路径 为页面指定一个图标路径, 在网页的 head 部分代码如下: <link rel="apple-touch-icon" href="/custom_icon.png"/> 为不同设备指定图标 在网页中为不同的设备指定特殊图标, 因为 iPhone 和 iPad 的图标尺寸不一样, 需要 sizes 属性来进行区分, 如果没有定义 sizes 属性, 默认 sizes 是 57 x 57. 代码如下: <link rel="apple-touch-icon" href="touch-icon-iphone.png" /> <link rel="apple-touch-icon" sizes="72x72" href="touch-icon-ipad.png" /> <link rel="apple-touch-icon" sizes="114x114" href="touch-icon-iphone4.png" /> 如果没有图片尺寸可以匹配设备图标的尺寸, 存在比设备图标大的图片, 将使用比设备图标尺寸略大的图片; 如果没有比设备图标大的图片, 则使用最大的图片. 如果没有在网页中指定图标路径, 将会在根目录搜寻以 apple-touch-icon... 和 apple-touch-icon-precomposed... 作为前缀的 PNG 图片. 假设现在有一个设备的图标大小是 57 x 57, 系统将按以下顺序搜寻图标: apple-touch-icon-57x57-precomposed.png apple-touch-icon-57x57.png apple-touch-icon-precomposed.png apple-touch-icon.png 主屏幕图标尺寸 iPhone 和 iPod (iTouch) 使用的图标尺寸: 57 x 57 pixels 114 x 114 pixels (高分辨率, iPhone 4 或以上使用) iPad 使用的图标尺寸: 72 x 72 pixels 主屏幕图标效果 当我们将图片作为主屏幕图标, iOS 系统会自动为图标加上视觉效果: 圆角 阴影 高亮 例如, 一个 57 x 57 的网站图标如下: 当用户将网页添加到主屏幕, 图标会加上上述效果, 如下: 所以我们无需对图片做太多处理, 提供符合尺寸的正方形图片即可. 无论是将图标放在默认位置上, 还是指定图标路径, 如果图标带有 -precomposed 后缀 (如: apple-touch-icon-precomposed.png), 系统将不会为你加上高亮效果. 后话 本文参考了 Safari 开发文档的两篇文章:《Configuring Web Applications》和《Custom Icon and Image Creation Guidelines》. 主要摘要了关于主屏幕图标的一些内容, 如果发现错漏和疑问, 请留言反馈. 我也为这个博客添加主屏幕图标, 同学们可以在设备中打开试试效果. 原文地址:http://www.neoease.com/add-web-icon-to-home-screen-on-iphone-or-ipad/
苹果safari浏览器当中apple-touch-icon-precomposed 和 apple-touch-icon属性是有区别的,之前在网上查了下相关的资料和苹果的开发文档手册,对这两中属性区别说的不够详细,导致现在大家开发的时候有些混淆。 苹果safari浏览器定义的这两种属性是为了苹果移动设备(ipod、ipad、iphone)对移动网站-mobile web进行收藏(“添加到桌面图标”)的时候增加的图标定义属性,当你建立一个移动网站(俗称:手机站,mobile web),避免不了为移动站的图标进行设置(这里有篇关于苹果meta设置详解的文章>>点击查看<<),在这篇文章当中尚未介绍这两中属性的区别,这篇文章就是弥补这一空缺。 图标设置的属性分为: apple-touch-icon 和 apple-touch-icon-precomposed两种属性,从字面意思上可以看出,第一个是 “苹果移动设备图标”,第二个为“苹果移动设备初始图标样式”,由于第二个的意思完全搞不明白预设是预设的什么究竟有什么不同的地方,从官方资料当中也没有搞懂有什么明显的不同,通过实验得知了这两个明显的差异。 直接上图: 图片一: 从图中(图片一)右下角可以看出有两个Milanoo图标,分别是apple-touch-icon 和 apple-touch-icon-precomposed属性的图标,看出区别了没? 放大看大图:注意图标上面的光泽感,看明白了吧,使用apple-touch-icon属性 的明显比使用apple-touch-icon-precomposed图标多出一个高光,因为在ios系统中对icon有一套规范,就是在ios设备的 图标统一为“四边圆角”、“高光处理”,至于“图标阴影”,是ios设备中统一为所有桌面元素增加的,所以不作为图标单独处理的样式,由于在视觉上统一最 重要的是形状的统一,所以“圆角”是必须的,但是对于“高光”苹果没有做出特别的强调,所以苹果设置当中把“高光”作为可选项,就产生了apple- touch-icon 和 apple-touch-icon-precomposed属性。 结论: 使用apple-touch-icon属性为“增加高光光亮的图标”; 使用apple-touch-icon-precomposed属性为“设计原图图标”; 大家牢记了,说了通篇感觉最有用的就是最后两句。哈哈。 连接一下Mobile WEB开发的相关文章: 手机网站开发必修课[1]:手机浏览器 手机网站开发必修课[2]:浏览器兼容性测试 手机网站开发必修课[3]:前端开发总结 触摸屏网站开发系列(一)-ios web App应用程序(ios meta)–ios meta详解 移动网站开发——CSS 手机网站前端开发布局技巧 用谷歌浏览器来当手机模拟器(模拟头信息)–开发时使用很方便 Android模拟器安装使用教程(附下载)–做浏览器兼容性测试用 原文地址:http://www.iptu.net/index.php/archives/3109.iptu
自动化测试另外一个比较重要的内容点是性能测试,很多问题可能有不止一种解决方案,很多时候并不知道哪个是最好的解决方案。例如有很多创建javascript对象的方法,使用javascript构造器、使用函数的方法或者使用闭包。我们可能会从可测试化、灵活性和性能的角度去考虑使用哪种方式。足见性能是相当重要的一点。 1.基准和相对性能 当一个问题我们有两个以上的解决方案的时候,判断哪个解决方案更好的原则很简单,就是哪个的性能更好。判断的原则也很简单:1.创建new Date()作为开始时间;2.执行要衡量的代码;3.代码执行完毕,创建new Date()作为结束时间,减去开始时间算出总时长;4.替换执行代码,重复上述步骤;5.比较各种方案的执行时长。 每个要比较的代码我们需要执行很多次,通常会把他们放在一个循环中。因为windows xp和vista操作系统的浏览器的timer的间隔时间是15ms,让这个问题变的更加复杂,测试会变的相当不准确,所以我们需要保证测试代码运行时长在500ms以上。 下面是我们用来做性能测试的代码,文件为benchmark.js: var ol;function runBenchmark(name, test) { if (!ol) { ol = document.createElement("ol"); document.body.appendChild(ol); } setTimeout(function () { var start = new Date().getTime(); test(); var total = new Date().getTime() - start; var li = document.createElement("li"); li.innerHTML = name + ": " + total + "ms"; ol.appendChild(li); }, 15); } 下面的代码使用runBenchmark()用来做测试,文件名为loops.js: var loopLength = 500000; // 填充循环数组 var array = []; for (var i = 0; i < loopLength; i++) { array[i] = "item" + i; } function forLoop() { for (var i = 0, item; i < array.length; i++) { item = array[i]; } }function forLoopCachedLength() { for (var i = 0, l = array.length, item; i < l; i++) { item = array[i]; } } function forLoopDirectAccess() { for (var i = 0, item; (item = array[i]); i++) { } } function whileLoop() { var i = 0, item; while (i < array.length) { item = array[i]; i++; } } function whileLoopCachedLength() { var i = 0, l = array.length, item; while (i < l) { item = array[i]; i++; } } function reversedWhileLoop() { var l = array.length, item; while (l--) { item = array[l]; } } function doubleReversedWhileLoop() { var l = array.length, i = l, item; while (i--) { item = array[l - i - 1]; } } // Run tests runBenchmark("for-loop", forLoop); runBenchmark("for-loop, cached length", forLoopCachedLength); runBenchmark("for-loop, direct array access", forLoopDirectAccess); runBenchmark("while-loop", whileLoop); runBenchmark("while-loop, cached length property", whileLoopCachedLength); runBenchmark("reversed while-loop", reversedWhileLoop); runBenchmark("double reversed while-loop", doubleReversedWhileLoop); 使用setTimeout方法的目的是,在测试中避免浏览器的堵塞。因为浏览器只有一个单线程,他用这一个线程完成javascript的运行、事件的触发和页面的渲染工作。timer则为浏览器提供了一个任务队列,可以用他来处理长时间运行的工作,这样就可以避免在运行测试代码时弹出超时警告。 下面我们看看loops.html页面的内容: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Relative performance of loops</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> </head> <body> <h1>Relative performance of loops</h1> <script type="text/javascript" src="benchmark.js"></script> <script type="text/javascript" src="loops.js"></script> </body> </html> 所有的测试做的事情都差不多,遍历数组,并访问每个成员。通过测试,我们可以看到哪种方式效率最高。 我们可以把runBenchmark方法做一下重构: var benchmark = (function () { function init(name) { var heading = document.createElement("h2"); heading.innerHTML = name; document.body.appendChild(heading); var ol = document.createElement("ol"); document.body.appendChild(ol); return ol; } function runTests(tests, view, iterations) { for (var label in tests) { if (!tests.hasOwnProperty(label) || typeof tests[label] != "function") { continue; } (function (name, test) { setTimeout(function () { var start = new Date().getTime(); var l = iterations; while (l--) { test(); } var total = new Date().getTime() - start; var li = document.createElement("li"); li.innerHTML = name + ": " + total + "ms (total), " + (total / iterations) + "ms (avg)"; view.appendChild(li); }, 15); }(label, tests[label])); } } function benchmark(name, tests, iterations) { iterations = iterations || 1000; var view = init(name); runTests(tests, view, iterations); } return benchmark; }()); 使用benchmark也很简单,如下: var loopLength = 100000; var array = []; for (var i = 0; i < loopLength; i++) { array[i] = "item" + i; } benchmark("Loop performance", { "for-loop": function () { for (var i = 0, item; i < array.length; i++) { item = array[i]; } }, "for-loop, cached length": function () { for (var i = 0, l = array.length, item; i < l; i++) { item = array[i]; } }, // ... "double reversed while-loop": function () { var l = array.length, i = l, item; while (i--) { item = array[l - i - 1]; } } }, 1000); 我们也可以为benchmark扩展更多的功能,例如把最快和最慢的测试用高亮的方式显示: // 记录次数 var times; function runTests (tests, view, iterations) { // ... (function (name, test) { // ... var total = new Date().getTime() - start; times[name] = total; // ... }(label, tests[label])); // ... }function highlightExtremes(view) { // The timeout is queued after all other timers, ensuring // that all tests are finished running and the times // object is populated setTimeout(function () { var min = new Date().getTime(); var max = 0; var fastest, slowest; for (var label in times) { if (!times.hasOwnProperty(label)) { continue; } if (times[label] < min) { min = times[label]; fastest = label; } if (times[label] > max) { max = times[label]; slowest = label; } } var lis = view.getElementsByTagName("li"); var fastRegexp = new RegExp("^" + fastest + ":"); var slowRegexp = new RegExp("^" + slowest + ":"); for (var i = 0, l = lis.length; i < l; i++) { if (slowRegexp.test(lis[i].innerHTML)) { lis[i].style.color = "#c00"; } if (fastRegexp.test(lis[i].innerHTML)) { lis[i].style.color = "#0c0"; } } }, 15); } // Updated benchmark function function benchmark (name, tests, iterations) { iterations = iterations || 1000; times = {}; var view = init(name); runTests(tests, view, iterations); highlightExtremes(view); } 2.分析和定位瓶颈 很多浏览器提供了调试工具,例如firefox的firebug。这些工具可以为我们提供很多信息,帮助我们跟踪代码,判断代码中的问题。例如下图就是使用firebug的实例。 代码下载:
TDD是一个迭代的开发过程,他包括下面的步骤:1.编写测试;2.运行测试,观察失败;3.确保测试通过;4.重构,减少重复。 每次迭代中,测试就是规范。在我们完成开发之后,测试就可以通过了。之后我们就需要进行减少重复代码和提高设计的重构工作,然后再次运行测试,并保证其通过。 虽然TDD不主张预期的大设计,但是我们在TDD开始之前还是需要做个简单的设计。我们要如何写自己的第一个设计呢?当我们获得了足够信息可以制定测试的时候,测试代码的编写,本身就是设计。我们指定在特定情况下特定代码的行为,系统之间组件如何响应,以及他们之间如何组合。下面我们会举例,以便大家更好的学习。 TDD中的迭代时间很短,我们需要非常清楚我们所处的阶段。无论何时我们对代码进行了修改,或者移出了某些功能,我们需要把他们记录在todo列表中,加以观察。这个列表可以是一张纸,或者记事本之类的东西,只要方便你快速的查找和记录既可。在处理这些新的修改点之前,我们需要首先结束本次迭代。本次迭代结束之后,我们再从todo列表中取出一个任务,开始下一次迭代工作。 步骤1:编写测试 每次TDD迭代,首先要做的事情是选择一个你要做的功能,为它编写单元测试。单元测试需要简短,测试聚焦在function上的某一个功能点上。比较好的编写测试的规则是,编写尽量少的测试代码就能让测试失败。当然,测试断言不能和之前的测试重复。如果一个测试涉及到系统的两个或两个以上的方面,就说明要么是这个测试太大了,要么是里面包含了重复的测试点。 测试需要能够描述我们实现的功能,我们的功能代码没做修改,就不需要修改测试代码。 假设我们要完成一个String.prototype.trim函数的开发,用以去除字符串前后空格。对该方法好的测试,第一步应该是测试前空格是否删除了。 testCase("String trim test", { "test trim should remove leading white-space": function () { assert("should remove leading white-space", "a string" === " a string".trim()); } }); 严谨起见,我们需要先判断字符串包含trim方法。这是因为我们添加的全局函数可能会和第三方代码发生冲突,在代码之前添加 typeof "".trim == "function",可以帮助我们在运行测试之前发现问题。 为单元测试提供输入条件,运行之后他会判断输出条件是否和预期一致。输入条件不仅仅是函数的参数,任何函数的依赖项,例如全局作用域、某些对象的特殊状态,这些都是输入条件。与此类似,输出结果包括返回值、全局作用域或者相关对象。我们通常把输入输出项分为直接和间接两种。直接项:函数的参数和返回值;间接项:不是以参数形式传入的参数和被修改的外部对象。 步骤2:观察失败的测试 测试准备好之后就可以运行了。有很多原因促使我们在编写实现代码之前运行测试,最主要的一点是我们可以用它来确定代码的状态。在写测试的时候,我们会有一个比较明确的期望,测试如何会失败。单元测试虽然不存在逻辑的分支,代码也比较简单,但他也像其他代码一样存在bug。但是运行它,比较期望结果,我们会很快发现这些bug。 理想情况下,当我们添加了新的测试的时候,我们应该可以运行所有测试用例。这样我们就可以很容易的抓取到干扰测试,例如一个测试依赖于另外一个测试。 编写实现代码之前运行测试,可以告诉我们一些新的事情。有时候会有这样的经历,在我们写任何实现代码之前测试可以正常通过。一般情况下,这样的事情不应该发生,TDD教导我们编写不能通过的测试。因为我们是先写测试代码,功能代码还没有开发,这时测试代码能跑通就说明存在问题。我们需要确定问题的来源,是不是运行环境已经提供了该方法的实现,或者我们有没有必要保留这条测试用例。 步骤3:确保测试通过 准备好运行失败的测试代码之后,我们要做的就是编写实现代码,并保证测试代码可以运行通过。有时候我们甚至需要硬编码,不必担心在这个步骤中我们的代码是多么糟糕,在后面的重构环节我们可以优化他。编写实现代码的时候,我们要寻找最明显的简洁实现方式,如果没有我们可以伪造他,而把具体的实现拖延到后面。 1.你不需要他 在极限编程中,TDD的精髓是“你不需要他”,意思是直到需要的时候才需要添加相关功能。基于假设添加一些以后可能会用到的代码,会让我们的代码变得很膨胀。对于动态语言,特别是javascript,违反这一原则有时候对我们是有诱惑的,他可以增加代码的灵活性。一个例子是,为函数添加过多的参数。除非有那样的需求,否则不要这样做。 2.通过String.prototype.trim测试 下面的代码是为满足之前的测试开发的。 String.prototype.trim = function () { return this.replace(/^\s+/, ""); }; 可以看到,这个实现还不完善,只去除了左空格。但是TDD就是这样的,每一步都很小,只要能让测试通过就可。发现新的需求点后,编写测试代码,然后完善实现代码并通过测试。 3.能够工作的最简单的方案 最简单的解决方案有时候意味着,可能要往产品中添加硬编码的代码。因为有时候一般的解决方案可能不是那么明显,我们可以用硬编码的方式推进我们的项目,等到有了解决方案的时候再替换。虽然硬编码可以推进我们的项目,代码质量是我们的最终目标。 步骤4:移除重复的重构 最后,最重要也是最有趣的工作就是使代码变得整洁。当实现代码开发完毕,测试顺利跑通之后,我们就可以考虑重构的工作了,把一些重复的代码移除。这期间只有一条准则:测试必须能跑通。关于重构好的建议是,每次只对一个操作进行修改,并保证测试能够通过。重构是对已有代码的维护修改,所以测试不能失败。 重复的代码可能会出现在不同位置,有时候他是为了解决硬编码的解决方案。如果我们有一个硬编码的假冒响应,我们需要给他添加另外的测试,让他在不同输入的条件下失败。或许我们一时还想不到替换硬编码的方案,但至少我们知道问题的存在,他为我们提供了足够的信息,方便我们找到最终解决方案。 重复的代码同样可能存在于测试代码中,例如setup中的请求对象和假冒的依赖。测试代码也是代码,同样需要维护,移除重复内容。如果测试代码和系统过于耦合,我们需要抽取帮助方法和对结构进行重构。setup和teardown可以用来集中设置对象的创建和销毁。 我们在重构的过程中不能让测试失败。如果在重构的过程中我们没有用更少的代码完成工作,我们就需要考虑把工作托到以后再做。 步骤5:重复工作 一旦所有工作都完成了,没有重复代码了,也没有重构工作需要做了,这时候就从todo列表中找一个新任务,重复上面的步骤。根据需要重复这样的工作。你熟悉了这一过程之后,可以放大脚步,但是确保你的周期很短,这样可以得到及时的反馈。 功能满足需求之后,我们可以考虑提高测试的覆盖率,可以添加对边界值的测试、对依赖项的测试、不同输入类型的测试、不同组件之间的整合测试等。下面是我们为String.prototype.trim添加的第二条测试: "test trim should remove trailing white-space": function () { assert("should remove trailing white-space", "a string" === "a string ".trim()); }
通过之前文件关于单元测试的介绍,我们学会了如何减少代码的缺陷,通过回归测试抓取bug,减少对公共测试的依赖提高开发效率。本章我们的关注点将会放在测试驱动开发(TDD)上,他是测试先行、开发置后的开发模式。他有很多的好处,例如:更好的可测试代码、更简洁的接口和可以提高开发者信心的代码质量。 测试驱动开发的目标和目的 测试驱动开发的目标是简洁的代码。TDD是一种迭代的开发过程,每个迭代式以写单元测试开始,单元测试可以作为将要开发功能的规范文档。短期迭代对代码的反馈是及时的,这样可以更容易发现我们不好的设计。编写开发代码之前,先写单元测试还可以提高单元测试覆盖率。 1.开发置后 传统的开发模式中,问题知道代码全部编写完才得以解决。理想情况下,代码会有整体的架构考虑,但是很多情况下,特别是JavaScript开发的过程中,情况往往不是这样。这种解决方案,通过去猜什么样的代码能解决问题,导致的结果是代码肿胀和紧耦合。如果没有单元测试,产品代码中可能会包含一些没有运行的代码,例如异常处理逻辑。还有就是边界值可能没有被测试到。 TDD把开发顺序颠倒了,首先做的不是编写功能实现代码,而是考虑目标的制定,要实现什么功能和如何实现。单元测试在这里起到规范和文档的作用。TDD的目标不是测试,所以他不能保证在边界值的处理上做的更好。TDD虽然产生了额外的单元测试代码,但是他提高了系统的健壮性,而且保证系统不包含不执行的代码。 2.在TDD中做设计 TDD的特点是“没有大的预先设计”,并不是“没有预先设计”。为了写出整洁的代码,我们需要衡量整个项目的持续时间和考虑开发的生命周期,所以我们需要制定计划。TDD不会无中生有的自动生成好的设计,但是他会帮助我们进化我们的设计。通过对单元测试的依赖,TDD更多的把关注点放在了相互独立的、隔离的组件上。这种方式可以帮我们我们写出接偶的代码,代码遵循单一职责和避免不必要的膨胀。TDD提供了严格控制,可以将很多设计的决定时间延迟到直到真正需要的时候。他可以很好的应对需求的变化,因为我们很少设计不需要的功能,或者不需要按照预先期望开发。 TDD驱使我们去处理设计,当有了新功能的时候,我们需要以单元测试的形式制定合理的用例。写单元测试时需要思考的,我们需要描述我们在解决什么样的问题。只有完成这个工作,才能开始编码。换句话说,TDD要求我们在提供解决方案之前,要先想想结果。 促进测试驱动开发 测试驱动开发最关键的是运行测试,测试需要能快速而容易的运行。如果不是这样的话,开发人员就会忽略测试,当开发了新功能之后也不会运行测试。这样会让开发变得一团糟。最糟糕的是,我们花费额外的时候使用TDD的开发模式,却没有起到我们期望的作用,我们开发的代码还是一团糟。所以,顺利的运行测试是相当重要的。 推荐的方案是使用自动测试(autotest),每个测试保存在独立的文件中,可以单独运行。他能在屏幕上显示测试结果,告诉我们哪些测试通过了(显示绿色),哪些测试正在运行,和哪些测试失败了(显示红色)。这样可以提高我们开发的效率,帮助我们重构代码,我们只需要关注测试失败的情况。 测试驱动开发的好处 1.能够工作的代码 TDD最大的好处就是生产可以工作的代码。一个基本的单元测试可以确保一段代码的稳定。可再生的单元测试在JavaScript中特别有用,我们需要在很多浏览器平台上进行测试。测试代码只需要写一次,通过测试可以很快找到不能工作的代码,发现bug。 2.遵循单一职责原则 在隔离的条件下描述和开发组件,能更容易的写出解耦和符合单一职责原则的代码。TDD的单元测试不需要测试组件的依赖,他们需要能用mock或stub的方式替换。另外,单元测试也可以帮助我们找到程序中紧耦合的代码。 3.迫使有意识的开发 因为在每次迭代之前,我们都是先写描述特定功能的测试,TDD就迫使我们在编写代码之前先进行思考。在解决问题之前先进行思考,有助于我们找到一个更可靠的解决方案。通过用例对功能进行描述,也有助于我们开发出更简明的代码。这样也会避免我们引入不必要的功能。 4.提高生产效率 如果你是第一次接触TDD,你会觉得所有的测试和步骤都需要你花费更多的时间。使用TDD从一开始也不是那么简单的事情,写出好的单元测试需要不断的练习,本系列课程会通过很多例子教会你如何完成这个工作。当你养成了好的TDD开发习惯的时候,他确实能提高你的开发效率。他可能需要你多花点时间去完成功能代码和测试代码的编写,但是他能减少你手工测试的时间,取而代之的是运行单元测试。最重要的是,你开发出了有单元测试保障的、能工作的代码,代码重构的工作将不会变得那么荆棘。你的工作会变得更快速、压力更少和更快乐。
FuncUnit相关的知识我在之前的博文已有简单介绍,大家可以自行阅读《javascriptMVC入门 -- 12.FuncUnit》。他提供了很多api方法,我的文章中没有涉及,大家可以去官网查看,地址:http://www.javascriptmvc.com/。 今天我们将通过一个简单的例子,介绍如何把FuncUnit引入项目中,对无法执行单元测试的js文件进行自动化的功能测试。我例子是对jquery焦点图插件进行测试,先来看一下项目结构: 蓝框选中的就是测试文件。根目录下面的documentjs、funcunit、jquery、steal四个文件夹对应于javascriptMVC框架源代码文件夹。 channel/focusScroll/funcunit下面的js文件是测试脚本。funcunit.js是入口文件,has-prev-next-btn@scroll-left.js和scroll-top@thumb-click.js测试脚本分别对应的测试页面是has-prev-next-btn@scroll-left.html和scroll-top@thumb-click.html。 FuncUnit.Master母版页里面包含QUnit相关内容: <head runat="server"> <title> <%=this.Page.Title %> FuncUnit Test</title> <link rel="stylesheet" type="text/css" href="/funcunit/qunit/qunit.css" /> <asp:ContentPlaceHolder ID="head" runat="server"> </asp:ContentPlaceHolder> </head> <body> <h1 id="qunit-header"> <%=this.Page.Title %> Test Suite</h1> <h2 id="qunit-banner"> </h2> <div id="qunit-testrunner-toolbar"> </div> <h2 id="qunit-userAgent"> </h2> <ol id="qunit-tests"> </ol> </body> funcunit.aspx是FuncUnit.Master的子页面,用来加载funcunit/funcunit.js。 <asp:Content ID="head" ContentPlaceHolderID="head" runat="server"> <script type='text/javascript' src='/steal/steal.js?channel/focusScroll/funcunit'></script> </asp:Content> funcunit/funcunit.js加载测试js脚本,运行测试。 steal("funcunit", "jquery") .then('./has-prev-next-btn@scroll-left.js') .then('./scroll-top@thumb-click.js') 我们来看has-prev-next-btn@scroll-left.js都做了什么事情。 说句题外话,大家文件命名的时候最好能体现出代码功能,一眼就知道他是干什么的。就像‘has-prev-next-btn@scroll-left.js’,一看我就知道他主要的测试关注点是:prev和next按钮是否工作正常,图片切换是左右切换。除文件命名外,还可以通过注释来描述测试的功能点包括哪些,这样方便日常维护。我们来看代码: /* 主要测试点: 1.页面包含prev-next按钮,测试相关功能是否正常 2.图片切换方式为左右切换,测试图片测试时候正常 3.测试导航图片class属性是否正确 4.鼠标经过导航图片时(hover效果),大图是否正确切换 */ steal("jquery", function () { module("test", { setup: function () { S.open("//channel/focusScroll/has-prev-next-btn@scroll-left.html"); } }); //设置导航图片功能 test("thumb hover works", function () { S("#myTab_btns li:nth-child(1)").move("#myTab_btns li:nth-child(2)"); S("#myTab_btns li:nth-child(2)").move("#myTab_btns li:nth-child(3)"); S("#main li:nth-child(3)").offset({ top: 0, left: 0 }).then(function () { ok(S("#main li:nth-child(2)").offset().left === -755); ok(S("#main li:nth-child(4)").offset().left === 755); equal(S("#myTab_btns li:nth-child(2)").hasClass('hot'), false); equal(S("#myTab_btns li:nth-child(3)").hasClass('hot'), true); }); }); //测试‘prev’按钮 test("prev btn works", function () { S("#btnPrev").click(); S("#main li:nth-child(4)").offset({ top: 0, left: 0 }).then(function () { ok(S("#main li:nth-child(1)").offset().left === -755*3); equal(S("#myTab_btns li:nth-child(4)").hasClass('hot'), true); equal(S("#myTab_btns li:nth-child(1)").hasClass('hot'), false); }); }); //测试‘next’按钮 test("next btn works", function () { S("#btnNext").click(); S("#main li:nth-child(2)").offset({ top: 0, left: 0 }).then(function () { ok(S("#main li:nth-child(1)").offset().left === -755); ok(S("#main li:nth-child(3)").offset().left === 755); equal(S("#myTab_btns li:nth-child(1)").hasClass('hot'), false); equal(S("#myTab_btns li:nth-child(2)").hasClass('hot'), true); }); }) }) 下面我来对代码中一些关键点做简单说明。 1. module的setup会在每次运行test方法之前运行,S.open用来打开一个窗口。 2. S("#myTab_btns li:nth-child(1)").move("#myTab_btns li:nth-child(2)");实现的功能是,从导航图1移动到导航图2,会触发鼠标移入移出的事件。 3. S("#main li:nth-child(3)").offset({ top: 0, left: 0 }).then(function () {...}); 等待第三章大图的offset变为{ top: 0, left: 0 }后,执行then里面的方法。在编码的过程中我在这里碰到了钉子,看官网api,他说offset()是一个wait类型的函数。那么就应该支持 .offset({ top: 0, left: 0 }, function () {...}) 的写法,但是代码运行错误。我去官网发帖求助也没有得到任何帮忙。之后通过看源代码才发现有then()方法,随后问题得以解决。 我们来看项目运行之后的结果,红框选中的是QUnit显示的测试报告(FuncUnit基于QUnit),蓝框选中的是FuncUnit运行时打开的测试页面(has-prev-next-btn@scroll-left.html 和 scroll-top@thumb-click.html)。 demo项目无法直接运行,因为javascriptMVC源代码有30M,博客园提供的上传文件空间有限,所以我只上传了测试相关代码。需要首先下载javascriptMVC源代码,替换到demo中的funcunit、steal和jquery文件夹,才能运行项目。 demo下载地址:FuncUnit_Test.zip
本篇我们将通过对Date.strftime编写单元测试的例子,学会断言、测试用例函数的相关知识。 首先我们先来看Date.strftime的实现代码。 Date.prototype.strftime = (function () { function strftime(format) { var date = this; return (format + "").replace(/%([a-zA-Z])/g, function (m, f) { var formatter = Date.formats && Date.formats[f]; if (typeof formatter == "function") { return formatter.call(Date.formats, date); } else if (typeof formatter == "string") { return date.strftime(formatter); } return f; }); } // 内部帮助函数 function zeroPad(num) { return (+num < 10 ? "0" : "") + num; } Date.formats = { // Formatting 方法 d: function (date) { return zeroPad(date.getDate()); }, m: function (date) { return zeroPad(date.getMonth() + 1); }, y: function (date) { return zeroPad(date.getYear() % 100); }, Y: function (date) { return date.getFullYear(); }, // Format 速记方式 F: "%Y-%m-%d", D: "%m/%d/%y" }; return strftime; }()); Date.prototype.strftime函数整体是一个即时匿名函数,该函数会自动执行,把strftime函数作为结果返回。strftime()通过正则表的式判断格式化标示符,返回正确的结果。 zeroPad()在1-9的数据前加0 。Date.formats 对象提供一系列辅助方法,用来处理时间。 如果代码有哪里看不懂可以给我留言,我会尽量讲解。 断言 单元测试的核心是断言,通过它的自动运行来比较开发者对于系统的预期结果是否和执行结果一致。两者一致则说明我们的系统一切正常,否则就存在问题,需要我们进一步确认。我们先来看一个简单的断言函数: function assert(message, expr) { if (!expr) { throw new Error(message); } assert.count++; return true; } assert.count = 0; 上面的函数只是简单的验证第二个参数是否是真值(除了false, null, undefined, 0, "", 和 NaN这些值之外的都可以)。断言成功,assert.count会加1,失败则抛出异常。 我们可以使用他来测试strftime() 方法。 var date = new Date(2009, 9, 2); try { assert("%Y should return full year", date.strftime("%Y") === "2009"); assert("%m should return month", date.strftime("%m") === "10"); assert("%d should return date", date.strftime("%d") === "02"); assert("%y should return year as two digits", date.strftime("%y") === "09"); assert("%F should act as %Y-%m-%d", date.strftime("%F") === "2009-10-02"); console.log(assert.count + " tests OK"); } catch (e) { console.log("Test failed: " + e.message); } 在自动化测试中,我们经常使用绿色代表成功,红色代表失败。我们可以创建一个output方法输出不同颜色的信息条,代替console.log的功能。 function output(text, color) { var p = document.createElement("p"); p.innerHTML = text; p.style.color = color; document.body.appendChild(p); } // 可以用它来代替上面断言例子中的 console.log output(assert.count + " tests OK", "#0c0"); // 失败的时候使用下面的方法: output("Test failed: " + e.message, "#c00"); 测试用例函数 在创建断言函数之后,我们需要创建测试用例函数,以便把断言组织起来。测试用例里面包含很多子的测试方法,每个测试方法针对被测对象的一部分行为进行测试。下面我们看一个简单的测试用例函数的实现,其中name是测试用例的名称,tests是一组测试方法,其中每个测试方法以‘test’开头。 function testCase(name, tests) { assert.count = 0; var successful = 0; var testCount = 0; for (var test in tests) { if (!/^test/.test(test)) { continue; } testCount++; try { tests[test](); output(test, "#0c0"); successful++; } catch (e) { output(test + " failed: " + e.message, "#c00"); } } var color = successful == testCount ? "#0c0" : "#c00"; output("<strong>" + testCount + " tests, " + (testCount - successful) + " failures</strong>", color); } 下面我们使用上面的方法测试strftime()。 var date = new Date(2009, 9, 2); testCase("strftime test", { "test format specifier %Y": function () { assert("%Y should return full year", date.strftime("%Y") === "2009"); }, "test format specifier %m": function () { assert("%m should return month", date.strftime("%m") === "10"); }, "test format specifier %d": function () { assert("%d should return date", date.strftime("%d") === "02"); }, "test format specifier %y": function () { assert("%y should return year as two digits", date.strftime("%y") === "09"); }, "test format shorthand %F": function () { assert("%F should act as %Y-%m-%d", date.strftime("%F") === "2009-10-02"); } }); 这组测试方法针对strftime()不同功能点进行测试,组合起来实现了对其全部功能的测试,最后把测试结果展现给用户。可以说效果还是相当不错的。 Setup 和 Teardown 接下来我们介绍setup()和teardown()方法的实现。这两个方法分别在tests执行之前和之后运行,setup()可以用来初始化测试条件,teardown()可以用来销毁相关条件。下面我们完善之前的testCase()方法,添加对setup和teardown()的支持。 function testCase(name, tests) { assert.count = 0; var successful = 0; var testCount = 0; var hasSetup = typeof tests.setUp == "function"; var hasTeardown = typeof tests.tearDown == "function"; for (var test in tests) { if (!/^test/.test(test)) { continue; } testCount++; try { if (hasSetup) { tests.setUp(); } tests[test](); output(test, "#0c0"); if (hasTeardown) { tests.tearDown(); } successful++; } catch (e) { output(test + " failed: " + e.message, "#c00"); } } var color = successful == testCount ? "#0c0" : "#c00"; output("<strong>" + testCount + " tests, " + (testCount - successful) + " failures</strong>", color); } 下面我们用改进后的testCase()重新测试strftime()方法。 testCase("strftime test", { setUp: function () { this.date = new Date(2009, 9, 2, 22, 14, 45); }, "test format specifier Y": function () { assert("%Y should return full year", this.date.strftime("%Y") == 2009); }, // ... }); 总结 本文提供的断言和测试用例函数比较简单,相信大家一看就会,其他复杂的测试框架则要相对复杂的多。但是他们在基本原理上没有太大差别,只是代码实现方式不同,再者就是api提供的更多些。本文主要目地在于抛砖引玉,向大家介绍测试框架中相关方法大致的实现方式,以及如何使用测试框架进行单元测试。要想更好的精通单元测试,除了继续加强理论知识的学习、不断的实践之外,还可以看看其他框架的源代码,相信对你定会有比较大的帮助。
从今天开始,我将以读书笔记的方式向大家介绍《Test-Driven JavaScript Development》相关内容。我不太清楚这本书是否已经有了中文的译本,有兴趣的朋友可以去搜索下,或者直接读英文原版。因为是读书笔记,算是供大家参考学习的资料,所以文章中很多知识或者概念的定义或者讲解可能不会那么精确。只要大家能明白我表达的内容即可,没必要太较真。 之前我写了《QUnit系列》有兴趣的朋友可以去阅读,他是当前比较流行的javascript测试的框架。通过学习QUnit相关知识,觉得很有必要丰富下自己的理论知识。只有有了强大的理论基础,你在工作上才会更有指导性和针对性。 本文将介绍单元测试的相关知识,单元测试的好处和他面临的困难。 什么是单元测试 简单而言,单元测试是用来测试生产代码的一段代码,他通过在已知状态下设置一个或多个对象,执行测试代码,检查输出结果与期望值是否一致。他应该具有下面一些特点:容易开发、快速运行、在隔离的环境中测试软件组件(每个测试之间不能相互影响,保持原子性)、可重复执行等。开发单元测试的过程中,我们可能还需要使用 mock 或者 stub 方式去模拟依赖操作。 单元测试应该在下面的情况下运行: • 代码开发完成之后,验证相关功能的正确性; • 代码发生改变的时候,验证它的行为没有发生改变; • 当系统添加了新的单元项时, 确保他的功能正确,并保证没有对系统造成影响。 在实际的工作中,大家可以使用市面上已有的测试框架进行开发,没有必要自动再动手制作自己的框架。这样做,一则可以减少开发时间、提高开发速度,再则别人框架已经提供了完善的相关api、框架也比较稳定,你不能保证你的框架就一定比别人的好。可选择的框架比较多,例如:QUnit,JSUnit等,大家可以自行找资料学习。如果学习QUnit,可以参照我之前的博文《QUnit系列》,或者去官网学习。 单元测试的好处 测试是一份投资,然而有些人反对单元测试,觉得他是在浪费时间。写单元测试确实需要时间,但是如果不使用它,有更好的办法吗。我们能做的恐怕只有执行一遍遍效率低下的手工测试了,而且还不一定能保证对系统的完全测试,要么就是直到问题产生的时候再测试。显而易见,单元测试还是有它的价值的,写一次测试代码,运行多次,当然有时候也需要对测试代码本身加以维护。 1.回归测试 在开发的过程中我们难免会犯错误产生一些bug,这些bug直到生产环境才发现。有时候我们修复了这个bug,但是产品发布的过程中也许会犯别的错误,导致这个bug没有修复或者再次出现。回归测试可以帮助我们解决这个问题。在产品发布之前,优先运行它,能够帮助我们即时捕获问题,得以确保产品质量。而且随着系统的变化,系统会变的越来越大,业务也许会变的越来越复杂。传统的人工回归测试可能难以应对(并不是说人工测试变的不重要),自动化测试会变得越发重要。 关于这点我深有体会,因为之前的公司对产品质量相当看重,系统建立了一整套机制帮助开发和维护人员及时发现系统存在的问题。这套机制包括:记录系统错误日志、自动化测试、集[代码嵌入-单元测试-demo服务器发布]于一体的build系统、还有就是传统的人工测试。在产品发布之前会先运行自动化测试,确保demo环境中的系统核心功能没有受影响。自动化测试通过之后才能进行产品发布。产品发布之后,会再次运行自动化测试,确保现网核心功能没有问题。自动化测试对保证产品质量起到了相当重要的作用。 当然,自动化测试有它的优势也有不足,所以还需要进行人工测试,共同保证产品的质量。 2.重构 重构就是在不影响代码功能的前提下,对他进行修改。例如:从一个方法中抽取一个帮助方法,保证代码的重用性,这就是重构;再或者对象或者方法的名字进行了修改,这也是重构。重构对于不断演进的系统,保持其架构设计的健壮性、重用性、灵活性有着相当重要的作用。当然,你需要在重构之后运行单元测试,这样才能保证你的重构对系统没有产生不良的影响。 3.跨浏览器测试 对于web开发者来说,我们开发的代码是要在不同的平台、不同的客户端上运行的。使用单元测试可以有效的减小我们测试的工作量。正所谓写一次测试代码,运行多次,我们可以在不同平台的不同客户端上运行我们的测试代码,保证我们的产品在不同环境下正常工作。 4.其他好处 精心编写的测试,对于系统来说相当于一份很好的说明文档,他可以帮助新来的开发人员更快的了解系统和相应功能。此外,通过编写测试,也可以帮助开发人员理清代码开发的思路。 单元测试面临的困难 些单元测试其实不是件容易的事,写出良好的单元测试需要不断的实践,是件具有挑战的事情。上节中提到的单元测试的好处,是建立在遵循最佳实践的基础上获得的。如果你写的是糟糕的单元测试,情况则完全不同,你会发现这种测试完全是在浪费时间,而且增加了维护成本。 为了写出好的单元测试,你必须保证你的代码是可测试的。当你为一个可测试性不好的系统引入测试的时候,你会发现你的工作完全是一项不可完成的挑战。反过来也说明,单元测试对于暴露紧密耦合的代码和分离关注点是有帮助的。 本书会通过一些具体的实例,来向大家介绍如何编写可测试的代码,以及如何写出好的单元测试。 学学单元测试真的挺好,除了可以用来提高产品质量外,对于提高自己代码书写质量还是有帮助的,可以让自己养成很好的代码编写习惯。
分析别人的源代码,除了可以了解程序功能是如何实现之外,还可以学到一些比较先进的编程方式和思想,进而提高自己的水平。本着这一想法,我将对QUnit的源代码加以解读,也希望对大家js水平的提高有个帮助作用。 好的js框架在语言上总是很干练的,里面也使用了很多比较先进的编程技巧,这就要求读者必须要有比较扎实的js基础知识。在这里我重点推荐汤姆大叔的译作《深入理解JavaScript系列》。文章很多共有50多篇,前20多篇对js基础知识作了很深入的讲解(市面上没看到比他更深入的书籍和博文,也可能是我看的资料少的缘故),后20多篇讲的是js的设计模式,我重点推荐前20多篇。相信通过对该系列重复的阅读(一次读不懂不怕,多读几次总会有感觉的),一定会让你的js水平有个质的提升。 现在我们言归正传,开始解读QUnit源代码。我们首先来看QUnit源码的大致结构: (function( window ) { var QUnit, config, ...; function Test( settings ) { } Test.count = 0; Test.prototype = { }; QUnit = { module: function( name, testEnvironment ) { }, asyncTest: function( testName, expected, callback ) { }, test: function( testName, expected, callback, async ) { }, expect: function( asserts ) { }, start: function( count ) { }, stop: function( count ) { } }; QUnit.assert = { ok: function( result, msg ) { }, equal: function( actual, expected, message ) { }, notEqual: function( actual, expected, message ) { }, deepEqual: function( actual, expected, message ) { }, notDeepEqual: function( actual, expected, message ) { }, strictEqual: function( actual, expected, message ) { }, notStrictEqual: function( actual, expected, message ) { }, "throws": function( block, expected, message ) { } }; extend( QUnit, QUnit.assert ); ... if ( typeof exports === "undefined" ) { extend( window, QUnit ); window.QUnit = QUnit; } ... QUnit.load = function() { }; addEvent( window, "load", QUnit.load ); function addEvent( elem, type, fn ) { if ( elem.addEventListener ) { elem.addEventListener( type, fn, false ); } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, fn ); } else { fn(); } } function extend( a, b ) { for ( var prop in b ) { if ( b[ prop ] === undefined ) { delete a[ prop ]; // 因为设置window.constructor避免IE8发生 "Member not found" 的错误 } else if ( prop !== "constructor" || a !== window ) { a[ prop ] = b[ prop ]; } } return a; } ... function id( name ) { return !!( typeof document !== "undefined" && document && document.getElementById ) && document.getElementById( name ); } ... // 获取全局对象 }( (function() {return this;}.call()) )); 框架整体式一个即时匿名函数,也叫立即执行匿名函数。 (function(){ ... }(args)) 他的特点是代码在解析之后会自动执行,本身又是一个闭包环境,内部变量不会对全局变量造成污染。这种方式是大多数第三方类库使用的开发方式,例如jquery,值得大家在自己的项目中实践。此外我们还注意到即使匿名函数传递的参数: (function() {return this;}.call()) call方法执行时候的上下文是null,this会返回global,也就是返回window对象。具体的原因可以通过阅读博文《深入理解JavaScript系列(13):This? Yes,this!》找到答案。 代码之后定义了一些内部使用的变量。接下来定义的是Test对象,会在QUnit.test()中使用,稍后的文章我会加以介绍。 下来的内容就是重头戏了,他定义了QUnit常用的api方法和相关的断言方法。对于断言方法你也许会感到奇怪,因为他是定义在QUnit.assert中的。而我们在单元测试中使用的时候,前面并没有添加QUnit前缀,这是怎么回事呢。原因是源码中使用了扩展方法,把QUnit.assert中的方法扩展到了QUnit中。 extend( QUnit, QUnit.assert ); 扩展方法位于稍后的位置,我们来看他是如何实现的。 function extend( a, b ) { for ( var prop in b ) { if ( b[ prop ] === undefined ) { delete a[ prop ]; // 因为设置window.constructor避免IE8发生 "Member not found" 的错误 } else if ( prop !== "constructor" || a !== window ) { a[ prop ] = b[ prop ]; } } return a; } 可以说这个方法实现的中规中矩,这是一种很通用的实现扩展或者继承的实现方式。就是简单的把b中存在的属性复制给了a,函数最后然后返回a对象。extend( QUnit, QUnit.assert ) 实现的功能,相信大家一定已经清楚了。源码中很多实现扩展的地方都使用了这个方法。 接下来QUnit使用下面的语句把本身暴露给了window对象,这样我们才能在单元测试中访问到api相关的方法。例如在单元测试中,我们可以直接使用module和test方法,前面不用添加QUnit前缀,就是下面的代码起的作用。QUnit把这些属性复制给了window。你直接使用的module和test其实就是window.module 和 window.test。 if ( typeof exports === "undefined" ) { extend( window, QUnit ); window.QUnit = QUnit; } 源码中定义了事件注册的方法:addEvent()。代码中使用addEvent( window, "load", QUnit.load )实现对window load事件的绑定,当页面加载完毕之后执行QUnit对象的加载操作。 QUnit.load = function() { }; addEvent( window, "load", QUnit.load ); 最后,我们通过对id方法的讲解介绍点js的一些小技巧。这里有两个值得学习的技巧,第一个是!!(双感叹号)的语法,他相当于一个三元运算符,返回的结果是Boolean值,可以参照我的博文:《js双感叹号判断》。第二个是多 && 判断,他会判断多个判断条件是否都成立,在此前提下返回最后一个判断对象,像下面的代码会返回document.getElementById。 function id( name ) { return !!( typeof document !== "undefined" && document && document.getElementById ) && document.getElementById( name ); }
测试用户操作 问题 那些依赖于用户操作的代码,不能通过执行函数来测试。通常元素的事件使用异步函数,例如click,这些需要模拟。 解决方案 你可以使用jQuery的 trigger()方法来触发事件,然后测试预期的行为。如果你不想浏览器事件被触发,你可以使用triggerHandler()来执行事件相关方法。这对于测试链接的click事件是有帮助的,因为trigger()可能会使浏览器改变地址栏信息,这恐怕不是测试过程中想要发生的。假设我们有一个简单的KeyLogger需要测试: function KeyLogger( target ) { if ( !(this instanceof KeyLogger) ) { return new KeyLogger( target ); } this.target = target; this.log = []; var self = this; this.target.off( "keydown" ).on( "keydown", function( event ) { self.log.push( event.keyCode ); }); } 我们可以手动的触发keypress事件,然后观察logger是否工作: test( "keylogger api behavior", function() { var event, $doc = $( document ), keys = KeyLogger( $doc ); // trigger event event = $.Event( "keydown" ); event.keyCode = 9; $doc.trigger( event ); // verify expected behavior equal( keys.log.length, 1, "a key was logged" ); equal( keys.log[ 0 ], 9, "correct key was logged" ); }); 讨论 如果你的事件处理不依赖任何特定的事件属性,你可以调用trigger(eventType)。但如果你的事件处理依赖于特殊的事件属性,你就需要使用$.Event创建一个事件对象,并设置必要的属性。对于复杂的行为触发相关事件是非常重要的,例如dragging,他由mousedown、mousemove和mouseup组成。即使是看起来很简单的事件也有可能是有很多事件组成的,例如click是由mousedown、mouseup和click组成的。你是否需要触发所有三个事件,依赖于你的测试代码,只触发click大多数情况是满足要求的。 如果那些仍然不够,你就需要一个框架来帮你模拟用户事件了: syn "是一个合成的事件类库,用来处理大多数的 typing, clicking, moving 和 dragging,能准确的模拟用户的实际操作"。基于QUnit的FuncUnit使用了syn,用来对web站点做功能测试。 JSRobot - "一个web应用的测试工具,可以产生真正的敲击键盘,而不是简单的模拟JavaScript事件触发。允许通过敲击触发浏览器实际的事件,而这对于别的框架来说是办不到的事情"。 DOH Robot "提供一个 API,允许测试者使用真实的、跨平台的、系统级的输入事件自动运行 UI 测试 "。他为你提供了接近真实浏览器的事件,但是要使用到 Java applets来实现。 保持测试原子性 问题 当测试集中在一起的时候,原本应该通过的测试会失败,本该失败的测试会通过,因为之前测试的副作用,使测试结果失效。 test( "2 asserts", function() { var $fixture = $( "#qunit-fixture" ); $fixture.append( "<div>hello!</div>" ); equal( $( "div", $fixture ).length, 1, "div added successfully!" ); $fixture.append( "<span>hello!</span>" ); equal( $( "span", $fixture ).length, 1, "span added successfully!" ); }); 第一个append()添加了一个div,第二个equal()并没有把它考虑进去。 解决方案 使用test()方法保持测试的原子性,使每一个断言清洁而不存在副作用。你应该只依赖 #qunit-fixture元素内部的 fixture标签,修改和依赖其他东西将会存在副作用。 test( "Appends a div", function() { var $fixture = $( "#qunit-fixture" ); $fixture.append( "<div>hello!</div>" ); equal( $( "div", $fixture ).length, 1, "div added successfully!" ); }); test( "Appends a span", function() { var $fixture = $( "#qunit-fixture" ); $fixture.append("<span>hello!</span>" ); equal( $( "span", $fixture ).length, 1, "span added successfully!" ); }); QUnit会在每次测试之后重置 #qunit-fixture中的元素,移出已经存在的事件。如果你只是使用了fixture内部的元素,每次测试之后你不需要执行手工操作来保持它的原子性。 讨论 除了 #qunit-fixture和过滤("高效发展"会讲到)之外,QUnit还提供了noglobals标志,看看下面的测试: test( "global pollution", function() { window.pollute = true; ok( pollute, "nasty pollution" ); }); 一般情况下,测试会得到成功的结果。但是选中noglobals后ok()得到失败的结果,这是因为QUnit认为他污染了window对象。在任何时候都没有必要使用那个标志,但是在整合第三方类库的时候,他对于判断是否引起全局命名污染是有帮助的。而且他也能帮助去发现由于副作用引起的bug。 分组测试 问题 你已经分割了你所有的测试,来让他们保持原子性并不产生副作用,但是你想保证他们的逻辑可组织,它本身能以组的方式运行。 解决方案 你可以使用module()去把测试分组: module( "group a" ); test( "a basic test example", function() { ok( true, "this test is fine" ); }); test( "a basic test example 2", function() { ok( true, "this test is fine" ); }); module( "group b" ); test( "a basic test example 3", function() { ok( true, "this test is fine" ); }); test( "a basic test example 4", function() { ok( true, "this test is fine" ); }); module()后面的测试会被分在一个组里面,测试结果中每个测试的名称会被加上module名称。你还可以通过module名字选择特定测试来运行。 讨论 module()除了实现分组功能外,还可以用来提取通用代码,他接受一个可选的第二个参数,定义module每次运行测试开始和结束的行为。 module( "module", { setup: function() { ok( true, "one extra assert per test" ); }, teardown: function() { ok( true, "and one extra assert after each test" ); } }); test( "test with setup and teardown", function() { expect( 2 ); }); 你可以一起定义setup和teardown属性,或者只定义其中一个。再次调用modile()方法的时候,会重置掉之前方法定义的setup/teardown方法。 高效发展(Efficient Devlopment) 问题 当你的测试需要运行很长时间的时候(例如好几秒),你可以不想浪费时间等待结果。 解决方案 QUnit有一系列的方法可以解决这个问题。最有趣的一个是点击头部的 "Hide passed tests"选项,这样QUnit会只显示失败的测试,他不会对测试时间有影响,只是起到聚焦失败测试的作用。 另外一个有趣的特性是,QUnit会把失败的测试的名字保存在sessionStorage中(你的浏览器必须要支持),默认情况下这个特性是开启的,只是我们没有注意到他的存在。下次你再运行测试的时候,之前失败的测试会被先执行,但是他对输出结果的顺序没有影响,影响的只是执行顺序。结合使用 "Hide passed tests" 你可以立即得到失败的测试。 讨论 自动排序会默认发生,你以为着你的测试需要保持原子性,如果不能保证这点将会产生随机的异常。修复问题是个正确的解决方案,或者存在困难,你可以设置QUnit.config.reorder = false。 除了自动排序之外,还有一些手工可选项。可以点击测试后面的"Rerun"链接,运行单个测试。他会在url中添加"testNumber=N"字符串参数,N代表你点击的测试编号。你可以重刷页面,只运行那个测试,或者使用浏览器的返回按钮区运行所有测试。 运行module中的所有测试,几乎以相同的方式工作。除非你选择页眉右上角的module,他会在url中添加"module=N"字符串,N代表module的编号,例如:"?module=testEnvironment%20with%20object"。 文章来源:http://qunitjs.com/cookbook/
自动化测试软件对于开发来说是一个很重要的工具,而单元测试对于自动化测试来说是基本组成部分:软件的每一个组件或者单元可以在非人工介入的情况下,使用测试工具一遍遍的重复执行。换句话说,就是你可以写一次测试,然后不用付出额外成本的任意执行多次。 除了测试覆盖率带来的好处外,测试还可以指导软件设计,这就是TDD(基于测试驱动的设计):先有测试,后有开发代码。你开始写一个简单的测试,然后写实现代码并保证代码能通过测试。完成上述步骤后,扩展你的测试,让他覆盖更多设计功能,然后再编写实现代码。重复上面的步骤直到完成开发,你会发现你的实现代码和之前的版本已经非常不一样了。 JavaScript的单元测试和其他语言没什么不同,你需要一个提供测试运行器的小框架,他同时提供写测试用例的工具。 自动化单元测试 问题 你想自动化测试你的应用和框架,甚至想使用TDD的开发方式。你或许会想些一个自己的测试框架,但是那需要很多额外的工作,涉及到太多的细节,还需要处理在不同浏览器中测试JavaScript的问题。 解决方案 现在已经有很多JavaScript的单元测试框架了,例如你可以选择QUnit。QUnit是jquery使用的单元测试框架,而且他已经被广泛的使用在了不同的项目中。使用QUnit很简单,你只需要添加两个相关文件到你的html页面即可。QUnit包括qunit.js:测试运行器和测试框架,qunit.css:测试页面用于显示测试结果的css文件。 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>QUnit basic example</title> <link rel="stylesheet" href="/resources/qunit.css"> </head> <body> <div id="qunit"></div> <div id="qunit-fixture"></div> <script src="/resources/qunit.js"></script> <script> test( "a basic test example", function() { var value = "hello"; equal( value, "hello", "We expect value to be hello" ); }); </script> </body> </html> 在浏览器中打开上面的文件,显示结果如下: 唯一需要的标签是<body>中含有id="qunit-fixture"的<div>,他对于所有QUnit的测试都是必须的,即使这个div元素是空的,他为测试提供夹具(fixture),我们会在“保持测试原子性”中详细介绍。 有趣的部分是跟在测试运行器(qunit.js)后面的脚本标签,他包含一个test方法。他包含两个参数,第一个参数是字符串类型,表示测试名称,他将会显示在测试结果和方法上。第二个参数是一个函数,包含实际的测试代码。他包含一个或者多个断言,上面的例子包含两个断言:ok() 和 equal()。我们会在“断言结果”中详细介绍。 我们注意到这里没有使用到document-ready,这次因为测试运行器会把test()添加到测试队列中,测试用例会被延迟执行。 讨论 测试套件的页眉显示页面名称,所有测试通过的时候,显示绿条;当至少有一条测试失败的时候显示红条。有选择框可供过滤结果,此外还有一个蓝条用来显示浏览器信息。选择框中有"Hide passed tests",当测试很多的时候可以使用它隐藏成功的测试,只显示失败的测试。 选择“noglobals”,会让QUnit在每次测试的开始和结束的时候罗列window的所有属性,并比较不同点。如果存在属性的添加和删除操作,测试失败,并显示不同点信息。这样可以验证我们的测试代码和被测试代码没有暴露任何的全局属性。 “notrycatch”选择框的作用是,告诉QUnit不使用try-catch跑测试,当有异常抛出的时候,测试运行器会停止运行。但是你会获得一个内部异常,这样在我们使用老浏览器(例如ie6)做测试的时候会有帮助。 页眉之下是测试总结,显示测试总用时,成功和失败的测试总数。当测试还在运行的时候,他会显示哪个测试用例正在被执行。 页面的主体部分是测试结果,每个实体以名字开头,后面跟着失败数、成功数和总断言数。点击实体将会显示每一个断言,经常会显示期望值和实际值。最后的“Rerun”链接会单独运行测试实体。 断言结果 问题 任何单元测试的实际元素都是断言,测试的开发者需要使用测试框架,将期望值和运行测试获得的实际值进行比较。 解决方案 QUnit提供三种断言。 ok( truthy [, message ] ) ok()是最基本的方法,他只需要一个参数,如果参数等于true,断言成功,否则失败。例外他还接受额外的字符串参数,用于显示测试结果。 test( "ok test", function() { ok( true, "true succeeds" ); ok( "non-empty", "non-empty string succeeds" ); ok( false, "false fails" ); ok( 0, "0 fails" ); ok( NaN, "NaN fails" ); ok( "", "empty string fails" ); ok( null, "null fails" ); ok( undefined, "undefined fails" ); }); equal( actual, expected [, message ] ) equal()方法使用简单的比较符(==)来比较期望值和实际值。当他们相等的时候,断言成功,否则失败。当失败的时候,期望值和实际值都会显示,另外还显示消息。 test( "equal test", function() { equal( 0, 0, "Zero; equal succeeds" ); equal( "", 0, "Empty, Zero; equal succeeds" ); equal( "", "", "Empty, Empty; equal succeeds" ); equal( 0, 0, "Zero, Zero; equal succeeds" ); equal( "three", 3, "Three, 3; equal fails" ); equal( null, false, "null, false; equal fails" ); }); 使用ok() 和 equal()让我们更容易的找到失败的测试,因为测试失败的时候他会很明显的告诉我们哪个值导致了问题。当你需要使用严格比较(===)的时候,可以使用strictEqual()。 deepEqual( actual, expected [, message ] ) deepEqual()可以像equal()那样使用,但是他适用的场景更多。他不是使用简单比较符(==),他使用的是更精确的比较符(===)。这种情况下,undefined不等于null,0或者空字符串(“”)。他同时也比较对象的内容,{key: value} 等于 {key: value},甚至比较的两个对象有不同的实例。deepEqual()同样也处理NaN,dates,正则表达式,数组和函数,而equal()只检查对象实例。 test( "deepEqual test", function() { var obj = { foo: "bar" }; deepEqual( obj, { foo: "bar" }, "Two objects can be the same in value" ); }); 如果你不想明确的比较两个对象的内容仍然可以使用equal(),但是deepEqual()是更好的选择。 同步回调 问题 有时候你的代码可能会阻止回调断言的执行,导致测试无声无息的就失败了。 解决方案 QUnit提供了一个特殊的断言,定义了测试包含的总断言数。当测试结束的时候,断言总数不相等,无论其他断言的执行情况,都会返回失败。使用上也相当简单,在测试开始的时候调用expect(),只需要传递期望的断言数作为方法参数。 test( "a test", function() { expect( 2 ); function calc( x, operation ) { return operation( x ); } var result = calc( 2, function( x ) { ok( true, "calc() calls operation function" ); return x * x; }); equal( result, 4, "2 square equals 4" ); }); 另外一种方式是,把期望断言数作为他的第二个参数传给test(): test( "a test", 2, function() { function calc( x, operation ) { return operation( x ); } var result = calc( 2, function( x ) { ok( true, "calc() calls operation function" ); return x * x; }); equal( result, 4, "2 square equals 4" ); }); 实例: test( "a test", 1, function() { var $body = $( "body" ); $body.on( "click", function() { ok( true, "body was clicked!" ); }); $body.trigger( "click" ); }); 异步回调 问题 虽然expect()对于同步回调的测试是有帮助的,但他不能用来处理异步回调的测试,异步回调和测试运行器中的执行队列的执行相冲突。在测试代码中执行一个timeout、interval或者ajax请求的时候,测试运行器只是会继续执行测试用例剩余的代码,然后接着执行测试队列中剩余的用例,而不会去执行异步操作。 解决方案 我们不使用test(),取而代之将使用asyncTest(),当你的测试代码执行完毕准备继续的时候执行start()。 asyncTest( "asynchronous test: one second later!", function() { expect( 1 ); setTimeout(function() { ok( true, "Passed and ready to resume!" ); start(); }, 1000); }); 实例: asyncTest( "asynchronous test: video ready to play", 1, function() { var $video = $( "video" ); $video.on( "canplaythrough", function() { ok( true, "video has loaded and is ready to play" ); start(); }); }); 文章来源:http://qunitjs.com/cookbook/
JavaScript测试框架:QUnit 下面我们将介绍使用QUnit来完成前一章中的单元测试。 <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Refactored date examples</title> <link rel="stylesheet" href="../qunit.css" /> <script src="../qunit.js"></script> <script src="prettydate.js"></script> <script> test("prettydate basics", function() { var now = "2008/01/28 22:25:00"; equal(prettyDate(now, "2008/01/28 22:24:30"), "just now"); equal(prettyDate(now, "2008/01/28 22:23:30"), "1 minute ago"); equal(prettyDate(now, "2008/01/28 21:23:30"), "1 hour ago"); equal(prettyDate(now, "2008/01/27 22:23:30"), "Yesterday"); equal(prettyDate(now, "2008/01/26 22:23:30"), "2 days ago"); equal(prettyDate(now, "2007/01/26 22:23:30"), undefined); }); </script> </head> <body> <div id="qunit"></div> </body> </html> 运行实例 这里有三点需要注意。 首先,我们包含了三个文件:两个和 QUnit 相关(qunit.css and qunit.js) ,另一个是包含测试函数的prettydate.js。 其次,是断言部门的脚本。test方法只会调用一次,第一个参数是测试名称,第二个参数是实际的测试代码。然后代码定义了变量now,便于后面使用。接下来使用equal方法来执行断言操作,他是QUnit提供的一系列方法中的一个。第一个参数是函数执行后的结果,第二个参数是期望值,如果两个值一致则断言通过,否则就失败。 最后,页面body中包含一些和QUnit相关的标签,这些元素是可选的。如果我们使用了,QUnit会使用他们来输出结果。 输出结果: 包含失败的输出结果: 因为测试包含一个失败的断言,所以QUnit没有把结果收起来,我们可以马上看到错误。我们把期望值和实际值都显示了出来,还显示了他们的不同点,这样对于我们找到问题很有帮助。 重构:2 我们的断言还没结束,还没有判断“几个星期”的情况。 再次之前,我们再把代码重构下。现在的版本每次断言的时候,都会去调用 prettyDate,并传递 now 参数。 我们可以重构一个自定义的断言函数: test("prettydate basics", function() { function date(then, expected) { equal(prettyDate("2008/01/28 22:25:00", then), expected); } date("2008/01/28 22:24:30", "just now"); date("2008/01/28 22:23:30", "1 minute ago"); date("2008/01/28 21:23:30", "1 hour ago"); date("2008/01/27 22:23:30", "Yesterday"); date("2008/01/26 22:23:30", "2 days ago"); date("2007/01/26 22:23:30", undefined); }); 运行实例 这样我们就把prettyDate封装到了date函数中,date里面会提供now变量,这样外面使用的时候就不需要再传递了。这样会让我们的代码更简单。 测试DOM操作 现在prettyDate已经可以被很好的测试了,让我们回过头来看之前的例子。借助于window的load事件,我们可以使用prettyDate函数选择DOM元素并更新他们。和之前一样,我们需要重构他并使之能够测试。另外,我们把两个方法放到同一个模块中,避免命名空间的混乱并且让代码更容易维护。 <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Refactored date examples</title> <link rel="stylesheet" href="../qunit.css" /> <script src="../qunit.js"></script> <script src="prettydate2.js"></script> <script> test("prettydate.format", function() { function date(then, expected) { equal(prettyDate.format("2008/01/28 22:25:00", then), expected); } date("2008/01/28 22:24:30", "just now"); date("2008/01/28 22:23:30", "1 minute ago"); date("2008/01/28 21:23:30", "1 hour ago"); date("2008/01/27 22:23:30", "Yesterday"); date("2008/01/26 22:23:30", "2 days ago"); date("2007/01/26 22:23:30", undefined); }); test("prettyDate.update", function() { var links = document.getElementById("qunit-fixture").getElementsByTagName("a"); equal(links[0].innerHTML, "January 28th, 2008"); equal(links[2].innerHTML, "January 27th, 2008"); prettyDate.update("2008-01-28T22:25:00Z"); equal(links[0].innerHTML, "2 hours ago"); equal(links[2].innerHTML, "Yesterday"); }); test("prettyDate.update, one day later", function() { var links = document.getElementById("qunit-fixture").getElementsByTagName("a"); equal(links[0].innerHTML, "January 28th, 2008"); equal(links[2].innerHTML, "January 27th, 2008"); prettyDate.update("2008/01/29 22:25:00"); equal(links[0].innerHTML, "Yesterday"); equal(links[2].innerHTML, "2 days ago"); }); </script> </head> <body> <div id="qunit"></div> <div id="qunit-fixture"> <ul> <li class="entry" id="post57"> <p>blah blah blah...</p> <small class="extra"> Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z">January 28th, 2008</a></span> by <span class="author"><a href="/john/">John Resig</a></span> </small> </li> <li class="entry" id="post57"> <p>blah blah blah...</p> <small class="extra"> Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-27T22:24:17Z">January 27th, 2008</a></span> by <span class="author"><a href="/john/">John Resig</a></span> </small> </li> </ul> </div> </body> </html> prettydate2.js代码: var prettyDate = { format: function(now, time){ var date = new Date(time || ""), diff = (((new Date(now)).getTime() - date.getTime()) / 1000), day_diff = Math.floor(diff / 86400); if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 ) return; return day_diff === 0 && ( diff < 60 && "just now" || diff < 120 && "1 minute ago" || diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" || diff < 7200 && "1 hour ago" || diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") || day_diff === 1 && "Yesterday" || day_diff < 7 && day_diff + " days ago" || day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago"; }, update: function(now) { var links = document.getElementsByTagName("a"); for ( var i = 0; i < links.length; i++ ) { if ( links[i].title ) { var date = prettyDate.format(now, links[i].title); if ( date ) { links[i].innerHTML = date; } } } } }; 运行实例 prettyDate.update方法是从之前例子中提取出来的,他含有now参数,方法里面把now传给了prettyDate.format。基于QUnit的测试,开始的时候会把所有包含在#qunit-fixture中的元素找出来。<div id="qunit-fixture">…</div>是页面新加入的标签,这里我们放置之前例子使用的DOM标签,这样我们就可以用于测试了。你不用担心这里DOM元素的改变会影响到其他的测试,因为QUnit会为每次测试重置这些标签的。 这里你也许会有疑问,为什么测试页面没有显示<div id="qunit-fixture">…</div>的内容呢,原因是QUnit把他放在了一个离我们屏幕很远的地方,看截图: 重构:3 上面的代码还是有很多重复的内容,我们进一步重构,把测试DOM的测试抽到方法domtest中,方便多次调用: <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Refactored date examples</title> <link rel="stylesheet" href="../qunit.css" /> <script src="../qunit.js"></script> <script src="prettydate2.js"></script> <script> test("prettydate.format", function() { function date(then, expected) { equal(prettyDate.format("2008/01/28 22:25:00", then), expected); } date("2008/01/28 22:24:30", "just now"); date("2008/01/28 22:23:30", "1 minute ago"); date("2008/01/28 21:23:30", "1 hour ago"); date("2008/01/27 22:23:30", "Yesterday"); date("2008/01/26 22:23:30", "2 days ago"); date("2007/01/26 22:23:30", undefined); }); function domtest(name, now, first, second) { test(name, function() { var links = document.getElementById("qunit-fixture").getElementsByTagName("a"); equal(links[0].innerHTML, "January 28th, 2008"); equal(links[2].innerHTML, "January 27th, 2008"); prettyDate.update(now); equal(links[0].innerHTML, first); equal(links[2].innerHTML, second); }); } domtest("prettyDate.update", "2008-01-28T22:25:00Z", "2 hours ago", "Yesterday"); domtest("prettyDate.update, one day later", "2008/01/29 22:25:00", "Yesterday", "2 days ago"); </script> </head> <body> <div id="qunit"></div> <div id="qunit-fixture"> <ul> <li class="entry" id="post57"> <p>blah blah blah...</p> <small class="extra"> Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z">January 28th, 2008</a></span> by <span class="author"><a href="/john/">John Resig</a></span> </small> </li> <li class="entry" id="post57"> <p>blah blah blah...</p> <small class="extra"> Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-27T22:24:17Z">January 27th, 2008</a></span> by <span class="author"><a href="/john/">John Resig</a></span> </small> </li> </ul> </div> </body> </html> 运行实例 回到开始 重构之前,我们之前的代码会变的很简单: <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Final date examples</title> <script src="prettydate2.js"></script> <script> window.onload = function() { prettyDate.update("2008-01-28T22:25:00Z"); }; </script> </head> <body> <ul> <li class="entry" id="post57"> <p>blah blah blah...</p> <small class="extra"> Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z"><span>January 28th, 2008</span></a></span> by <span class="author"><a href="/john/">John Resig</a></span> </small> </li> <-- 更多内容... --> </ul> </body> </html> 运行实例 结论 测试JavaScript不简单是使用测试运行器,写写测试用例。他要求你需要对代码结构作比较大的改变,以方便他能被执行单元测试。我们通过一个例子演示了如何实现特定的测试框架,也介绍了如何重构代码,以使他能使用QUnit做测试。QUnit提供了很多功能,例如支持测试异步代码,例如timeouts,ajax和事件。他的可视化测试运行器可以帮助我们debug代码,对特性测试的重新执行,并且方便我们跟踪错误信息。 文章来源:http://qunitjs.com/intro/
最近我在重新设计自己的博客站点,决定用一个日历样式的icon显示时间。以前的解决方案一般是用背景图片,感谢css3,现在我们用css3就能实现这样的功能。我将会用到一些linear-gradients, border radius 和 box shadow这些属性来代替以前的photoshop设计。 photoshop 概念图 很多设计者采用直接在浏览器上设计的方式,但是我还是更喜欢先做photoshop的概念图的方式。虽然现在很多效果可以直接用css实现,但是用photoshop设计效果的方式比不断尝试修改css来最终达到你想要的效果的方式简单很多。 首先创建一个圆角矩形,设置圆角半径为10px,之后我们会用css的border-radius属性实现。 为矩形添加垂直方向的渐变,渐变颜色是从#dad8d8 到 #fcfcfc。 设置1像素的描边,颜色是#e3e3e3 最后添加向下的阴影效果,透明度为20%,0像素的距离和15像素的大小。这些效果在css中将会用box-shadow属性实现。 复制刚才的矩形,移除上边的部分。修改渐变,颜色从#790909 到 #d40000,填充新创建的矩形,这部分将要放置月份信息。 设置一个内阴影来表示上边框,颜色为#a13838,100%透明度,3px的距离和0px的大小。 用photoshop的字体工具设置日历icon上半部分时间内容的字体效果,字体为Helvetica,颜色为#9e9e9e。 在下面红色部分输入月份信息,字体设置为宽,颜色为白色。 photoshop的模型就完成了。以前的话,我们会把图片抽出来作为背景,再在上面写上html的数字,但是现在所有这些都可以用css实现。 HTML结构 <div class="date"> <p>25 <span>May</span></p> </div> 这次时间ICON demo的html非常简单。我们会用带有class为‘date’的div作为容器,然后用一个p标签来表示日期数字。天和月份在我们的设计里用不同大小的字符表示,所以我们会<span>标签来区别对待不同元素。 css样式 .date { width: 130px; height: 160px; background: #fcfcfc; background: linear-gradient(top, #fcfcfc 0%,#dad8d8 100%); background: -moz-linear-gradient(top, #fcfcfc 0%, #dad8d8 100%); background: -webkit-linear-gradient(top, #fcfcfc 0%,#dad8d8 100%); } css样式首先设置了整个容器的高和宽,通过css的gradient又可以很容易的实现渐变的效果。 .date { width: 130px; height: 160px; background: #fcfcfc; background: linear-gradient(top, #fcfcfc 0%,#dad8d8 100%); background: -moz-linear-gradient(top, #fcfcfc 0%, #dad8d8 100%); background: -webkit-linear-gradient(top, #fcfcfc 0%,#dad8d8 100%); border: 1px solid #d2d2d2; border-radius: 10px; -moz-border-radius: 10px; -webkit-border-radius: 10px; } 用border属性可以实现photoshop中1px边框效果,然后用border-radius实现了圆角的效果。不要忘记加上-moz-和-webkit-前缀,以实现对老版本浏览器的兼容。 .date { width: 130px; height: 160px; background: #fcfcfc; background: linear-gradient(top, #fcfcfc 0%,#dad8d8 100%); background: -moz-linear-gradient(top, #fcfcfc 0%, #dad8d8 100%); background: -webkit-linear-gradient(top, #fcfcfc 0%,#dad8d8 100%); border: 1px solid #d2d2d2; border-radius: 10px; -moz-border-radius: 10px; -webkit-border-radius: 10px; box-shadow: 0px 0px 15px rgba(0,0,0,0.1); -moz-box-shadow: 0px 0px 15px rgba(0,0,0,0.1); -webkit-box-shadow: 0px 0px 15px rgba(0,0,0,0.1); } 最后一部分代码,通过box-shadow实现在photoshop设计中的下阴影效果。添加0px的水平和垂直的偏移量,增加15px的模糊度。用rgba实现对透明度的控制,在photoshop设计中的105,在这里换成了0.1。 .date p { font-family: Helvetica, sans-serif; font-size: 100px; text-align: center; color: #9e9e9e; } 我们用给p标签定义样式,实现了为日期定义文字样式。字体,文字大小,文字颜色都是从photoshop中拷贝得到的,text-align设置为居中。但是样式也同样影响了月份文字,接下来我们会单独为它定义span标签样式。 .date p span { background: #d10000; background: linear-gradient(top, #d10000 0%, #7a0909 100%); background: -moz-linear-gradient(top, #d10000 0%, #7a0909 100%); background: -webkit-linear-gradient(top, #d10000 0%, #7a0909 100%); } 红色部分的实现是通过为span的背景设置linear-gradient属性实现的,红色的数值也是来自于photoshop。 .date p span { background: #d10000; background: linear-gradient(top, #d10000 0%, #7a0909 100%); background: -moz-linear-gradient(top, #d10000 0%, #7a0909 100%); background: -webkit-linear-gradient(top, #d10000 0%, #7a0909 100%); font-size: 45px; font-weight: bold; color: #fff; text-transform: uppercase; display: block; } 修改文字样式,使它和设计匹配,大小设置为45px,设置为粗体字,颜色设置为白色,使用text-transform实现大写转换。将span标签设置为块元素,这样他就会匹配容器的大小了,设置红色背景。 .date p span { background: #d10000; background: linear-gradient(top, #d10000 0%, #7a0909 100%); background: -moz-linear-gradient(top, #d10000 0%, #7a0909 100%); background: -webkit-linear-gradient(top, #d10000 0%, #7a0909 100%); font-size: 45px; font-weight: bold; color: #fff; text-transform: uppercase; display: block; border-top: 3px solid #a13838; border-radius: 0 0 10px 10px; -moz-border-radius: 0 0 10px 10px; -webkit-border-radius: 0 0 10px 10px; padding: 6px 0 6px 0; } 剩下的就是添加头部边框,用border-top样式实现,还有就是用border-radius属性实现下部两个圆角。一点点的padding属性可以让月份文字上下和其他元素有些间隔。 浏览器兼容性 尽管css改进的属性可以帮助我们实现photoshop中渐变和阴影的效果,但是我们仍然要面对以前web设计师要面对的问题,浏览器兼容性。 原文地址:http://line25.com/tutorials/how-to-create-a-cool-blog-post-date-icon-with-css
以前,在web上要显示灰度图片的话,只有手工使用图片软件转换。但是现在借助于html5的canvas可以实现这个过程,而不需要再借助图片编辑软件了。我用html5和jquery做了一个demo,来展示如何实现这个功能。 demo地址:http://webdesignerwall.com/demo/html5-grayscale/ 目的 这个demo将会向你展示用html5和jquery,如何实现鼠标在图片上移动移出时,灰度图像和原图之间的切换。在html5出现之前,要实现这个功能就需要准备两个图片,一个灰度图片,一个原图。但是现在借助于html5可以实现的更快更容易,因为灰度图片是直接在原图上生成的。我希望这段js代码对你在创建文件或者图片陈列功能的时候有帮助作用。 效果图 jquery 代码 下面的jquery代码将会寻找目标图片,并生成一个灰度的版本。当你鼠标移动到图片上时,灰度图片会变成原色。 <script src="jquery.min.js" type="text/javascript"></script> <script type="text/javascript"> // 设置 window load事件是为了等待所有图片加载完毕之后才行运行 $(window).load(function(){ // 使图片渐入,这样有颜色的原图就不会显示出来了,然后再执行window load 事件 $(".item img").fadeIn(500); // 复制图片 $('.item img').each(function(){ var el = $(this); el.css({"position":"absolute"}).wrap("<div class='img_wrapper' style='display: inline-block'>").clone().addClass('img_grayscale').css({"position":"absolute","z-index":"998","opacity":"0"}).insertBefore(el).queue(function(){ var el = $(this); el.parent().css({"width":this.width,"height":this.height}); el.dequeue(); }); this.src = grayscale(this.src); }); // 使图片渐入 $('.item img').mouseover(function(){ $(this).parent().find('img:first').stop().animate({opacity:1}, 1000); }) $('.img_grayscale').mouseout(function(){ $(this).stop().animate({opacity:0}, 1000); }); }); // 使用canvas制作灰色图片 function grayscale(src){ var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); var imgObj = new Image(); imgObj.src = src; canvas.width = imgObj.width; canvas.height = imgObj.height; ctx.drawImage(imgObj, 0, 0); var imgPixels = ctx.getImageData(0, 0, canvas.width, canvas.height); for(var y = 0; y < imgPixels.height; y++){ for(var x = 0; x < imgPixels.width; x++){ var i = (y * 4) * imgPixels.width + x * 4; var avg = (imgPixels.data[i] + imgPixels.data[i + 1] + imgPixels.data[i + 2]) / 3; imgPixels.data[i] = avg; imgPixels.data[i + 1] = avg; imgPixels.data[i + 2] = avg; } } ctx.putImageData(imgPixels, 0, 0, 0, 0, imgPixels.width, imgPixels.height); return canvas.toDataURL(); } </script> 如何使用 依照下面的步骤: 引用jquery.js 复制上面的代码 设置目标图片(eg: .post-img, img, .gallery img, etc.) 你也可以设置动画的速度(ie. 1000 = 1 second) 兼容性 我尝试了所有支持html5和canvas的浏览器,例如:Chrome, Safari, 和 Firefox。如果是不支持html5的浏览器,他只会用原图,不会生成灰度图片。 注意:如果本地html文件不能在firefox和chrome上运行的话,你就需要将html文件部署到服务器上去了。 自我实践 我自己按照教程测试了下,发现些需要注意的事项,使用firefox打开页面,程序不能正确运行,但是将相关代码部署到服务器之后可以运行。 必须保证是本地图片,不然要报Security error。 这是因为: Canvas是HTML5标准中的画布元素,可以用来绘制2D和3D图像. 但是在调试的时候很容易遇到Security error问题. 目前我在调试时遇到过的Security error主要是出现在toDataURL()和src上. Security error说明这段代码没有语义问题,但因为安全原因无法正常运行. throw Security error的情况: 在Canvas中使用跨域图片 在本地无服务器环境下进行调试 无法获取当前域与图片的关系 在stackoverflow上查到的一些解决方法通常是让你解决跨域问题. 但实际上如果你本地调试时不使用服务器软件也会造成这个问题. 例如: 本地调试时使用toDataURL功能,此时的Canvas中使用了本地的图片文件.在Chrome和Firefox中仍然会throw security error. 常见的解决方法是在本地架设一个服务器环境,或者将内容提交到服务器上再进行调试. 原文地址:http://webdesignerwall.com/tutorials/html5-grayscale-image-hover
大家都知道单元测试对于保证代码质量的重要性,但是对客户端代码进行单元测试则要困难的多。一个比较棘手的问题是,因为JavaScript代码和后台代码或者html结合的比较紧密,他缺少真正单元的概念。例如对dom的操作,无论我们是借助jquery这样的类库,把js代码单独放在一个文件,还是直接使用内嵌代码的实现方式,都没有可以测试的单元。 那么什么是单元呢。一般而言,单元就是一个功能函数,相同的输入,输出结果是一定的。这种情况的函数,做单元测试是相当简单的,但有时候你需要处理一些特殊情况,例如对dom的操作。对于我们来说他仍然是有用的,我们可以指出哪些代码可以构造到单元里面,然后对他做相应的测试。 创建单元测试 有了上面的指导思想,对于我们开始一项全新工作,并引入单元测试时相当简单的工作。不过本文介绍的内容是,帮助你对已有代码完善单元测试,我们需要解决下面的难题:提取现有代码,对重要部分作测试;发现潜在问题并加以修复。 提取现有代码把他放到不同地方,而不影响现有功能,我们把这一过程称为重构,重构是改善代码设计相当有用的方式。任何对代码的修改都有可能影响现有功能,这也就体现了单元测试的重要性,他会让你的工作更有保障。而这时候我们还没有单元测试,所以需要借助手工测试的方式来保证任何代码的修改没有产生新的bug。我们现在有了理论基础,接下来要做的就是找个例子来实践下。下面的代码会找到所有包含 title 属性的连接, 然后根据情况显示过去了多少时间,例如: “5 days ago”: <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Mangled date examples</title> <script> function prettyDate(time){ var date = new Date(time || ""), diff = (((new Date()).getTime() - date.getTime()) / 1000), day_diff = Math.floor(diff / 86400); if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 ) return; return day_diff == 0 && ( diff < 60 && "just now" || diff < 120 && "1 minute ago" || diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" || diff < 7200 && "1 hour ago" || diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") || day_diff == 1 && "Yesterday" || day_diff < 7 && day_diff + " days ago" || day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago"; } window.onload = function() { var links = document.getElementsByTagName("a"); for ( var i = 0; i < links.length; i++ ) { if ( links[i].title ) { var date = prettyDate(links[i].title); if ( date ) { links[i].innerHTML = date; } } } }; </script> </head> <body> <ul> <li class="entry" id="post57"> <p>blah blah blah...</p> <small class="extra"> Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z"><span>January 28th, 2008</span></a></span> by <span class="author"><a href="/john/">John Resig</a></span> </small> </li> <!-- 更多内容... --> </ul> </body> </html> 如果你运行代码,你会发现不是所有的时间会被替换。代码会查询页面中所有包含title属性的连接,然后对title执行prettyDate函数,如果函数返回结果则更新链接的innerHTML属性。 让代码变得可测试 问题在于对于大于31天的时间,prettyDate只是返回undefined,链接的内容不会发生变化。如果要看假定发生了什么,我需要硬编码一个当前时间。 <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Mangled date examples</title> <script> function prettyDate(now, time){ var date = new Date(time || ""), diff = (((new Date(now)).getTime() - date.getTime()) / 1000), day_diff = Math.floor(diff / 86400); if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 ) return; return day_diff == 0 && ( diff < 60 && "just now" || diff < 120 && "1 minute ago" || diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" || diff < 7200 && "1 hour ago" || diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") || day_diff == 1 && "Yesterday" || day_diff < 7 && day_diff + " days ago" || day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago"; } window.onload = function() { var links = document.getElementsByTagName("a"); for ( var i = 0; i < links.length; i++ ) { if ( links[i].title ) { var date = prettyDate("2008-01-28T22:25:00Z", links[i].title); if ( date ) { links[i].innerHTML = date; } } } }; </script> </head> <body> <ul> <li class="entry" id="post57"> <p>blah blah blah...</p> <small class="extra"> Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z"><span>January 28th, 2008</span></a></span> by <span class="author"><a href="/john/">John Resig</a></span> </small> </li> <-- 更多内容... --> </ul> </body> </html> 运行实例 现在链接应该显示“2 hours ago”, “Yesterday”等,但是现在的代码仍然不是实际可测试的单元。在没有进一步改造代码的前提下,我们能测试的只是DOM的改变结果。虽然这样可以工作,但是对html代码很小的修改都有可能打破测试,对于这种测试的效费比很差。 重构:1 我们需要对代码重构,以使他变得可以单元测试。我们需要做两点改变:1.以参数的形式向prettyDate函数传递当前时间,这样程序里面就不需要使用new Date了;2.把函数抽到一个单独的文件中,这样他就变得可重用了。 <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Refactored date examples</title> <script src="prettydate.js"></script> <script> window.onload = function() { var links = document.getElementsByTagName("a"); for ( var i = 0; i < links.length; i++ ) { if ( links[i].title ) { var date = prettyDate("2008-01-28T22:25:00Z", links[i].title); if ( date ) { links[i].innerHTML = date; } } } }; </script> </head> <body> <ul> <li class="entry" id="post57"> <p>blah blah blah...</p> <small class="extra"> Posted <span class="time"><a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z"><span>January 28th, 2008</span></a></span> by <span class="author"><a href="/john/">John Resig</a></span> </small> </li> <-- 更多内容... --> </ul> </body> </html> prettydate.js代码: function prettyDate(now, time){ var date = new Date(time || ""), diff = (((new Date(now)).getTime() - date.getTime()) / 1000), day_diff = Math.floor(diff / 86400); if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 ) return; return day_diff == 0 && ( diff < 60 && "just now" || diff < 120 && "1 minute ago" || diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" || diff < 7200 && "1 hour ago" || diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") || day_diff == 1 && "Yesterday" || day_diff < 7 && day_diff + " days ago" || day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago"; } 运行实例 现在我们就有些东西可以测试了,我们来做单元测试: <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Refactored date examples</title> <script src="prettydate.js"></script> <script> function test(then, expected) { results.total++; var result = prettyDate("2008/01/28 22:25:00", then); if (result !== expected) { results.bad++; console.log("Expected " + expected + ", but was " + result); } } var results = { total: 0, bad: 0 }; test("2008/01/28 22:24:30", "just now"); test("2008/01/28 22:23:30", "1 minute ago"); test("2008/01/28 21:23:30", "1 hour ago"); test("2008/01/27 22:23:30", "Yesterday"); test("2008/01/26 22:23:30", "2 days ago"); test("2007/01/26 22:23:30", undefined); console.log("Of " + results.total + " tests, " + results.bad + " failed, " + (results.total - results.bad) + " passed."); </script> </head> <body> </body> </html> 运行实例(确保允许使用控制台,例如firebug或者chrome的web inspector) 这样我们就创建了一个特定的测试架构,使用控制台输出结果。他不依赖DOM,所以我们可以在一个没有浏览器的JavaScript环境中运行它,例如Node.js或者Rhino。测试失败,会显示出期望值和实际值。最后会显示出失败和成功总数。如果全部成功,结果应该类似于这样: Of 6 tests, 0 failed, 6 passed. 失败的情况: Expected 2 day ago, but was 2 days ago. Of 6 tests, 1 failed, 5 passed. 虽然我们可以使用特定的解决方案完成测试,但是借助已有的测试框架可以帮助我们更好的完成工作,他为我们提供了更好的结果展示,更多的命令等等。下节我们将介绍QUnit的使用。 文章来源:http://qunitjs.com/intro/
今天看qunit源代码,发现一段很奇怪的代码,虽然能领会他的意思,但是不明白双感叹号起到的作用。 function id( name ) { return !!( typeof document !== "undefined" && document && document.getElementById ) && document.getElementById( name ); } 然后去网上查了些资料,他相当于三元运算符,返回boolean值。 var ret = !!document.getElementById 等价于: var ret = document.getElementById ? true : false; 当值是非空字符串和非零数字返回true,当值是空字符串、0或者null返回false。 var a = " "; alert(!!a); //true var a = "s"; alert(!!a); //true var a = true; alert(!!a); //true var a = 1; alert(!!a); //true var a = -1; alert(!!a); //true var a = -2; alert(!!a); //true var a = 0; alert(!!a); //false var a = ""; alert(!!a); //false var a = false; alert(!!a); //false var a = null; alert(!!a); //false