Placeholder(占位符) 是 HTML5 新增的一個 HTML 屬性,用來對可輸入字段的期望值提供提示信息,目前已經得到主流瀏覽器的廣泛支持,使用方式非常簡單:
<input id="username" name="username" placeholder="請輸入用戶名" type="text">
該屬性適用於 <textarea>
多行文本框和 type
屬性值為 text, password, search, tel, url 或者 email 等的 <input>
。
自定義樣式
如果想改變 placeholder 的默認呈現樣式,應該使用 ::placeholder
偽元素選擇器,不過當前還沒有瀏覽器支持,因此只能根據不同瀏覽器的不同實現方式分別定義:
::-webkit-input-placeholder { /* Chrome/Safari/Opera */
color: green;
}
::-moz-placeholder { /* Firefox 19+ */
color: green;
}
:-ms-input-placeholder { /* IE 10+ 注意這里只有一個冒號 */
color: green;
}
為什么樣式要分別定義呢,如果像下面這樣組合到一起:
::-webkit-input-placeholder,
::-moz-placeholder {
color: green;
}
不應該把針對不同瀏覽器的選擇器寫在一起,這樣寫會因為無法識別的選擇器而造成這整個規則集被忽略(當然,像類似 IE 7 這種具有超強錯誤處理能力的瀏覽器除外,不過這里和 IE 7 沒什么關系)。
Firefox 定義方式的改變
如果需要兼容老版本的 Firefox 瀏覽器,還需要添加下面這種只有一個冒號的樣式規則:
:-moz-placeholder { /* Firefox 4 - 18 */
color: green;
}
因為從 Firefox 19 開始一個冒號的偽類定義方式 :-moz-placeholder
被廢棄了,切換為兩個冒號的偽元素定義方式。與此同時,它還添加了一個默認的 opacity: 0.54
不透明度樣式,如果需要,可以覆蓋掉該樣式,否則文字是半透明的:
::-moz-placeholder {
color: green;
opacity: 1;
}
偽類和偽元素
偽類和偽元素有什么區別呢?偽類可以理解為給某個元素添加了一個類,比如 :first-child
偽類,選擇第一個子元素:
p:first-child {
font-size: 16px;
}
也可以理解為元素當前的狀態,同樣也可以通過添加一個真正的 class 來實現類似效果:
p.first-child {
font-size: 16px;
}
無論是偽類還是真正的類,樣式都是直接添加到 <p>
元素上的。
而偽元素可以理解為添加了一個虛擬的元素。比如 p:before
偽元素,可以像下面這個偽代碼這樣理解:
<before>p:before</before>
<p>paragraph</p>
這里 <p>
元素和 p:before
可以理解為是兩個不同的元素。如果被繞暈了,沒關系,畢竟這不是本文的重點,更多偽元素與偽類的信息可以參考 Pseudo-classes - CSS | MDN 和 Pseudo-elements - CSS | MDN
關於偽類選擇器引發的問題
因為 IE 瀏覽器使用的是偽類 :-ms-input-placeholder
選擇器來定義 placeholder 的樣式,實際上樣式是作用於文本輸入框的,如果另外還有針對文本輸入框的選擇器特殊性更高的樣式規則,將會覆蓋掉該樣式,參考下面代碼:
input:-ms-input-placeholder { /* 0, 0, 1, 1 */
color: green;
}
#username { /* 0, 1, 0, 0 */
color: blue;
}
注釋中的數字用來表示該選擇器的特殊性。
上面兩個樣式規則當中使用 ID 選擇器的特殊性更高,所以在 IE 10/11 中測試會看到 placeholder 顯示為藍色,與期望的有點不一樣。同樣使用偽類選擇器的舊版本 Firefox 也會出現這個問題,因此,在書寫樣式的時候需要特別注意,實在搞不定,別忘了還有 !important
規則可以用。其它使用兩個冒號的偽元素選擇器的瀏覽器不會出現這個問題,例如:
input::-webkit-input-placeholder { /* 0, 0, 0, 2 */
color: green;
}
#username { /* 0, 1, 0, 0 */
color: blue;
}
上面兩個樣式規則互相之間不會影響,使用 Chrome 測試 placeholder 的顏色為綠色。
關於選擇器的特殊性可以參考拙作CSS選擇器特殊性與重要性。
讓行為保持一致
雖然樣式是可以自定義了,不過在行為上還有些差異,在 Chrome 和 Firefox 中當文本輸入框輸入內容時 placeholder 才會消失,清除內容時又會顯示出來;而在 IE 中則是當文本輸入框獲取焦點時 placeholder 就消失了,失去焦點同時沒有輸入內容時才會重新顯示。如果希望在 Chrome 和 Firefox 等瀏覽器中實現獲取焦點就消失的效果,可以借助 :focus
偽類選擇器來將 placeholder 的文本顏色設置為透明:
:focus::-webkit-input-placeholder {
color: transparent;
}
當文本輸入框獲取焦點時,placeholder 的顏色被設置為透明,感官上就好像消失一樣。
JavaScript
獲取或者修改 placeholder 的內容直接獲取或者修改對應文本輸入框的 placeholder
屬性的值就行了:
$('input').attr('placeholder', 'Please enter your name');
So easy,媽媽再也不用擔心我寫代碼了。不過,想要像普通 DOM 元素那樣通過 javaScript 獲取偽元素對象直接操作估計很難,目前可以使用 window.getComputedStyle()
方法來得到其樣式屬性,該方法的第二個參數是一個偽元素:
window.getComputedStyle(document.getElementById('username'),
'::-moz-placeholder').getPropertyValue('color'); // "rgb(0, 255, 0)"
如果要通過 JavaScript 來修改 placeholder 偽元素的樣式的話比較好的一種方式是預先定義好幾種不同的樣式:
.style-1::-moz-placeholder {
color: green;
}
.style-2::-moz-placeholder {
color: red;
}
然后通過切換文本輸入框的 class
屬性來實現修改樣式的目的:
$('input').addClass('style-2').removeClass('style-1');
另外也可以通過直接添加樣式規則來實現。
Polyfill
對於不支持該屬性的瀏覽器,會簡單地忽略掉。原則上,placeholder 僅僅是用來對期望的輸入起個提示的作用,對於不支持的瀏覽器在可用性上不受任何影響。如果需要兼容,那么應該使用特性檢測的方式,針對不支持的瀏覽器使用 Polyfill,對於支持的瀏覽器來說,原生的當然是最好。
判斷瀏覽器是否支持 <input>
元素的 placeholder
屬性,可以引入 Modernizr 庫來判斷:
if (!Modernizr.input.placeholder) {
// 做點什么事
}
也可以自己寫判斷,簡單易行的辦法就是生成一個 <input>
元素對象,並判斷該元素對象是否具有 placeholder
屬性:
'placeholder' in document.createElement('input')
同理,對於 <textarea>
元素也是一樣:
'placeholder' in document.createElement('textarea')
另外,Opera Mini 明明不支持 placeholder 屬性,卻裝成自己很懂的樣子。這時候可以使用客戶端檢測技術來將之排除掉,Opera Mini 的 window 對象包含一個 operamini 對象:
({}).toString.call(window.operamini) === '[object OperaMini]'
結合起來就是這樣:
if (!('placeholder' in document.createElement('input'))
|| ({}).toString.call(window.operamini) === '[object OperaMini]') {
// 做點什么事
}
在編寫 Polyfill 的時候應該盡量與原生功能保持一致,我這里選擇向 IE 的方式看齊,即當文本輸入框獲取或失去焦點的時候處理 placeholder 是否顯示,將文本輸入框的 value
值設置為 placeholder
的值來模擬顯示 placeholder 的狀態。再添加上事件處理程序,當文本輸入框獲取焦點時如果 value
的值為 placeholder 則清空文本輸入框;當文本輸入框失去焦點時如果 value
值為空則將 placeholder 的內容賦給它,同時當 placeholder 顯示的時候應該給文本輸入框添加一個 class="placeholder"
用來設置樣式以區別是顯示的 placeholder 和還是顯示的普通 value:
// 做點什么事
$('input[placeholder]').on('focus', function() {
var $this = $(this);
if (this.value === $this.attr('placeholder') && $this.hasClass('placeholder')) {
this.value = '';
$this.removeClass('placeholder');
}
}).on('blur', function() {
var $this = $(this);
if (this.value === '') {
$this.addClass('placeholder');
this.value = $this.attr('placeholder');
}
});
這只是一個簡單的模擬實現,實際上還有很多需要處理的情況。
處理密碼輸入框
如果需要處理 placeholder 的是個密碼輸入框,它的 value
值會顯示為圓點之類的字符,呈現幾個莫名其妙的圓點來作為 placeholder 提示恐怕不妥,因此需要特殊對待一下,將密碼輸入框拷貝一份出來然后修改其 type
屬性為 'text' 來替代顯示 placeholder,並把原本的密碼輸入框隱藏:
$('input[placeholder]').on('blur', function() {
var $this = $(this);
var $replacement;
if (this.value === '') { // 失去焦點時值為空則顯示 placeholder
if (this.type === 'password') {
$replacement = $this.clone().attr('type', 'text');
$replacement.data('placeholder-password', $this);
// 替代顯示的文本輸入框獲取焦點時將它刪掉,並且重新顯示原來的密碼輸入框
$replacement.on('focus', function() {
$(this).data('placeholder-password').show().focus();
$(this).remove();
});
$this.after($replacement).hide();
$this = $replacement;
}
$this.addClass('placeholder');
$this[0].value = $this.attr('placeholder');
}
});
對於 IE 8 來說它不支持修改 input 元素的 type 屬性,使用 jQuery 的 .attr() 方法來修改的話只會默默地失敗。此時,可以新建一個文本輸入框,然后把密碼輸入框上的屬性賦給這個新建的文本輸入框:
try {
$replacement = $this.clone().prop('type', 'text'); // 使用 .prop() 方法在 IE 8 下會報錯
} catch(e) {
$replacement = $('<input>').attr({
'type': 'text',
'class': this.className // 還可以賦予 id, name 等屬性
});
}
需要注意的是 id 和 name 屬性不要重復了,可以先用一個變量保存下來,再用 .removeAttr() 方法來刪除屬性。
處理表單提交
當表單提交的時候應該將那些正在顯示 placeholder 的文本輸入框過濾掉,畢竟沒有必要將那些沒有用的數據提交給服務器,在提交時候將那些文本輸入框的 value
值設為空,提交之后再恢復成顯示 placeholder 的狀態:
$(document).on('submit', 'form', function() {
var $input = $('.placeholder', this);
$input.each(function() {
this.value = '';
});
setTimeout(function() {
$input.each(function() {
this.value = $(this).attr('placeholder');
});
}, 10);
});
離開頁面時
當用戶離開頁面時也需要清空正在顯示 placeholder 的文本輸入框,防止瀏覽器記住文本輸入框當前的值,這里可以給 window 對象綁定一個 beforeunload
事件來處理:
$(window).on('beforeunload', function() {
$('.placeholder').each(function() {
this.value = '';
});
});
另外還需要考慮的問題:
- 使用代碼給一個文本輸入框賦值時,應該同時處理 placeholder 的狀態。
- 使用代碼獲取一個正在顯示 placeholder 的文本輸入框的值的時候應該得到的是一個空字符串。
如此多的問題也就是說無論 Polyfill 寫到如何極致,都很難與原生的功能相提並論,因此推薦的方式使用特性檢測技術僅針對不支持的瀏覽器做 Polyfill,而 Polyfill 的功能應盡可能地向原生功能看齊。
總結
將所有樣式總結起來:
input::-webkit-input-placeholder,
textarea::-webkit-input-placeholder {
color: #999;
}
input::-moz-placeholder,
textarea::-moz-placeholder {
color: #999;
opacity: 1;
}
input:-ms-input-placeholder,
textarea:-ms-input-placeholder {
color: #999;
}
.placeholder {
color: #999;
}
input:focus::-webkit-input-placeholder,
textarea:focus::-webkit-input-placeholder {
color: transparent;
}
input:focus::-moz-placeholder,
textarea:focus::-moz-placeholder {
color: transparent;
}
Polyfill 可以直接使用這個 jQuery 插件 mathiasbynens/jquery-placeholder,已經相當完善了。