精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

CSS Houdini:用瀏覽器引擎實現高級CSS效果

開發
在本文,我們會介紹Houdini的APIs以及它們的使用方法,看看這些API當前的支持情況,并給出一些在生產環境中使用它們的建議。

作者 | vivo 互聯網前端團隊-Wei Xing

Houdini被稱之為Magic of styling and layout on the web,看起來十分神秘,但實際上,Houdini并非什么神秘組織或者神奇魔法,它是一系列與CSS引擎相關的瀏覽器API的總稱。

一、Houdini 是什么

在了解之前,先來看一些Houdini能實現的效果吧:

反向的圓角效果(Border-radius):

圖片

動態的球形背景(Backgrond):

圖片

彩色邊框(Border):

圖片

神奇吧,要實現這些效果使用常規的CSS可沒那么容易,但對CSS Houdini來說,卻很easy,這些效果只是冰山一角,CSS Houdini能做的有更多。(這些案例均來自Google Chrome Labs,更多案例可以通過 Houdini Samples 查看)。

看完效果,再來說說Houdini到底是什么。

首先,Houdini 的出現最直接的目的是為了解決瀏覽器對新的CSS特性支持較差以及Cross-Browser的問題。我們知道有很多新的CSS特性雖然很棒,但它們由于不被主流瀏覽器廣泛支持而很少有人去使用。

隨著CSS規范在不斷地更新迭代,越來越多有益的特性被納入進來,但是一個新的CSS特性從被提出到成為一個穩定的CSS特性,需要經過漫長地等待,直到被大部分瀏覽器支持時,才能被開發者廣泛地使用。

而 Houdini 的出現正是洞察和解決了這一痛點,它將一系列CSS引擎API開放出來,讓開發者可以通過JavasScript創造或者擴展現有的CSS特性,甚至創造自己的CSS渲染規則,給開發者更高的CSS開發自由度,實現更多復雜的效果。

二、JS Polyfill vs Houdini

有人會問,實際上很多新的CSS特性在被瀏覽器支持之前,也有可替代的JavaScript Polyfill可以使用,為什么我們仍然需要Houdini呢?這些Polyfill不是同樣可以解決我們的問題嗎?

要回答這個問題也很簡單,JavaScript Polyfill相對于Houdini有三個明顯的缺陷:

不一定能實現或實現困難。CSSOM開放給JavaScript的API很少,這意味著開發者能做的很有限,只能簡單地操縱DOM并對樣式做動態計算和調整,光是去實現一些復雜的CSS新特性的Polyfill就已經很難了,對于更深層次的Layout、Paint、Composite等渲染規則更是無能為力。所以當一個新的CSS特性被推出時,通過JavaScript Polyfill不一定能夠完整地實現它。

實現效果差或有使用限制。JavaScript Polyfill是通過JavaScript來模擬CSS特性的,而不是直接通過CSS引擎進行渲染,通常它們都會有一定的限制和缺陷。例如,大家熟知的css-scroll-snap-polyfill就是針對新的CSS特性Scroll Snap產生的Polyfill,但它在使用時就存在使用限制或者原生CSS表現不一致的問題。

性能較差。JavaScript Polyfill可能造成一定程度的性能損耗。JavaScript Polyfill的執行時機是在DOM和CSSOM都構建完成并且完成渲染后,通常JavaScript Polyfill是通過給DOM元素設置內聯樣式來模擬CSS特性,這會導致頁面的重新渲染或回流。尤其是當這些Polyfill和滾動事件綁定時,會造成更加明顯的性能損耗。

圖片

Houdini的誕生讓CSS新特性不再依賴于瀏覽器,開發者通過直接操作CSS引擎,具有更高的自由度和性能優勢,并且它的瀏覽器支持度在不斷提升,越來越多的API被支持,未來Houdini必然會加速走進web開發者的世界,所以現在對它做一些了解也是必要的。

在本文,我們會介紹Houdini的APIs以及它們的使用方法,看看這些API當前的支持情況,并給出一些在生產環境中使用它們的建議。

Houdini的名稱與一位著名美國逃脫魔術師Harry Houdini的名稱一樣,也許正是取逃脫之意,讓CSS新特性逃離瀏覽器的掌控。

三、Houdini APIs

上文提到CSS Houdini提供了很多CSS引擎相關的API,根據Houdini提供的規范說明文件,API共分為兩種類型:high-level APIs 和 low-level APIs 。

圖片

high-level APIs:顧名思義是高層次的API,這些API與瀏覽器的渲染流程相關。

Paint API

  • 提供了一組與繪制(Paint)過程相關的API,我們可以通過它自定義的渲染規則,例如調整顏色(color)、邊框(border)、背景(background)、形狀等繪制規則。
  • Animation API

    • 提供了一組與合成(composite)渲染相關的API,我們可以通過它調整繪制層級和自定義動畫。

    Layout API

    • 提供了一組與布局(Layout)過程相關的API,我們可以通過它自定義的布局規則,類似于實現諸如flex、grid等布局,自定義元素或子元素的對齊(alignment)、位置(position)等布局規則。

    low-level APIs:低層次的API,這些API是high-level APIs的實現基礎。

    • Typed Object Model API
    • CSS Properties & Values API
    • Worklets
    • Font Metrics API
    • CSS Parser API

    這些APIs的支持情況在不斷更新中,可以看到當前最新的一次更新時間是在2021年5月份,還是比較活躍的。(注:圖片來源于Is Houdini ready yet? )

    圖片

    對比下圖2018年底的情況,Houdini目前得到了更廣泛的支持,我們也期待圖里更多綠色的板塊被逐漸點亮。

    圖片

    大家可以訪問 Is Houdini ready yet? 看到Houdini的最新支持情況。

    下文中,我們會著重介紹Typed Object Model API、CSS Properties & Values API、Worklets和Paint API、Animation API,因為它們目前具有比其他API更好的支持度,且它們的特性已經趨于穩定,在未來不會有很大的變更,大家也能在了解它們之后直接將它們使用在項目中。

    四、 Typed Object Model API

    在Houdini出現以前,我們通過JavaScript操作CSS Style的方式很簡單,先看看一段大家熟悉的代碼。

    // Before Houdini
    const size = 30
    target.style.fontSize = size + 'px' // "20px"


    const imgUrl = 'https://www.exampe.com/sample.png'
    target.style.background = 'url(' + imgUrl + ')' // "url(https://www.exampe.com/sample.png)"


    target.style.cssText = 'font-size:' + size + 'px; background: url('+ imgUrl +')'
    // "font-size:30px; background: url(https://www.exampe.com/sample.png)"

    我們可以看到CSS樣式在被訪問時被解析為字符串返回,設置CSS樣式時也必須以字符串的形式傳入。開發者需要手動拼接數值、單位、格式等信息,這種方式非常原始和落后,很多開發者為了節省性能損耗,會選擇將一長串的CSS Style字符串傳入cssText,可讀性很差,而且很容易產生隱蔽的語法錯誤。

    Typed Object Model與TypeScript的命名類似,都增加了Type這個前綴,如果你使用過TypeScript就會了解到,TypeScript增強了類型檢查,讓代碼更穩定也更易維護,Typed Object Model也是如此。

    相比于上面晦澀的傳統方法,Typed Object Model將CSS屬性值包裝為Typed JavaScript Object,讓每個屬性值都有自己的類型,簡化了CSS屬性的操作,并且帶來了性能上的提升。通過JavaScript對象來描述CSS值比字符串具有更好的可讀性和可維護性,通常也更快,因為可以直接操作值,然后廉價地將其轉換回底層值,而無需構建和解析 CSS 字符串。

    在Typed Object Model中CSSStyleValue是所有CSS屬性值的基類,在它之下的子類用于描述各種CSS屬性值,例如:

    • CSSUnitValue
    • CSSImageValue
    • CSSKeywordValue
    • CSSMathValue
    • CSSNumericValue
    • CSSPositionValue
    • CSSTransformValue
    • CSSUnparsedValue
    • 其它

    通過它們的命名就可以看出這些不同的子類分別用于表示哪種類型的CSS屬性值,以CSSUnitValue為例,它可以用于表示帶有單位的CSS屬性值,例如font-size、width、height,它的結構很簡單,由value和unit組成。

    {
    value: 30,
    unit: "px"
    }

    可以看到,通過對象來描述CSS屬性值確實比傳統的字符串更易讀了。

    要訪問和操作CSSStyleValue還需要借助兩個工具,分別是attributeStyleMap和computedStyleMap(),前者用于處理內聯樣式,可以進行讀寫操作,后者用于處理非內聯樣式(stylesheet),只有讀操作。

    // 獲取stylesheet樣式
    target.computedStyleMap().get("font-size"); // { value: 30, unit: "px"}


    // 設置內聯樣式
    target.attributeStyleMap.set("font-size", CSS.em(5));


    // stylesheet樣式仍然返回20px
    target.computedStyleMap().get("font-size"); // { value: 30, unit: "px"}


    // 內聯樣式已經被改變
    target.attributeStyleMap.get("font-size"); // { value: 5, unit: "em"}

    當然attributeStyleMap和computedStyleMap()還有更多可用的方法,例如clear、has、delete、append等,這些方法都為開發者提供了更便捷和清晰的CSS操作方式。

    五、CSS Properties & Values API

    根據MDN的定義,CSS Properties & Values API也是Houdini開放的一部分API,它的作用是讓開發者顯式地聲明自定義屬性(css custom properties),并且定義這些屬性的類型、默認值、初始值和繼承方法。

    --my-color: red;
    --my-margin-left: 100px;
    --my-box-shadow: 3px 6px rgb(20, 32, 54);

    在被聲明之后,這些自定義屬性可以通過var()來引用,例如:

    // 在:root下可聲明全局自定義屬性
    :root {
    --my-color: red;
    }
    #container {
    background-color: var(--my-color)
    }

    了解了自定義屬性的基本概念和使用方式后,我們來考慮一個問題,我們能否通過自定義屬性來幫助我們完成一些過渡效果呢?

    例如,我們希望為一個div容器設置背景色的transition動畫,我們知道CSS是無法直接對background-color做transition過渡動畫的,那我們考慮將transition設置在我們自定義的屬性--my-color上,通過自定義屬性的漸變來間接完成背景的漸變效果,是否能做到呢?根據剛才的自定義屬性簡介,也許你會嘗試這么做:

    // DOM
    <div id="container">container</div>
    // Style
    :root {
    --my-color: red;
    }
    #container {
    transition: --my-color 1s;
    background-color: var(--my-color)
    }
    #container:hover {
    --my-color: blue;
    }

    這看起來是個符合邏輯的寫法,但實際上由于瀏覽器不知道該如何去解析--my-color這個變量(因為它并沒有明確的類型,只是被當做字符串處理),所以也無法對它采用transition的效果,因此我們并不能得到一個漸變的背景色動畫。

    圖片

    但是,通過CSS Properties & Values API提供的CSS.registerProperty()方法就可以做到,就像這樣:

    // DOM
    <div id="container">container</div>
    // JavaScript
    CSS.registerProperty({
    name: '--my-color',
    syntax: '<color>',
    inherits: false,
    initialValue: '#c0ffee',
    });
    // Style
    #container {
    transition: --my-color 1s;
    background-color: var(--my-color)
    }
    #container:hover {
    --my-color: blue;
    }

    與上面的不同之處在于,CSS.registerProperty()顯式定義了--my-color的類型syntax,這個syntax告訴瀏覽器把--my-color當做color去解析,因此當我們設置transition: --my-color 1s時,瀏覽器由于提前被告知了該屬性的類型和解析方式,因此能夠正確地為其添加過渡效果,得到的效果如下圖所示。

    圖片

    CSS.registerProperty()接受一個參數對象,參數中包含下面幾個選項:

    • name: 變量的名字,不允許重復聲明或者覆蓋相同名稱的變量,否則瀏覽器會給出相應的報錯。
    • syntax: 告訴瀏覽器如何解析這個變量。它的可選項包含了一些預定義的值等。
    • inherits: 告訴瀏覽器這個變量是否繼承它的父元素。
    • initialValue: 設置該變量的初始值,并且將該初始值作為fallback。

    在未來,開發者不僅可以在JavaScript中顯式聲明CSS變量,也可以直接在CSS中直接聲明:

    @property --my-color{
    syntax: '<color>',
    inherits: false,
    initialValue: '#c0ffee',
    }

    六、Font Metrics API

    目前 Font Metrics API 還處于早期的草案階段,它的規范在未來可能會有較大的變更。在當前的specification文件中,說明了 Font Metrics API 將會提供一系列API,允許開發者干預文字的渲染過程,創建文字或者動態修改文字的渲染效果等。期待它能在未來被采納和支持,為開發者提供更多的可能。

    七、CSS Parser API

    目前 Font Metrics API 也處于早期的草案階段,當前的specification文件中說明了它將會提供更多CSS解析器相關的API,用于解析任意形式的CSS描述。

    八、Worklets

    Worklets是輕量級的 Web Workers,它提供了讓開發者接觸底層渲染機制的API,Worklets的工作線程獨立于主線程之外,適用于做一些高性能的圖形渲染工作。并且它只能被使用在HTTPS協議中(生產環境)或通過localhost來啟用(開發調試)。

    Worklets不像Web Workers,我們不能將任何計算操作都放在Worklets中執行,Worklets開放了特定的屬性和方法,讓我們能處理圖形渲染相關的操作。我們能使用的Worklet類型暫時有如下幾種:

    • PaintWorklet - Paint API
    • LayoutWorklet - Animation API
    • AnimationWorklet - Layout API
    • AudioWorklet - Audio API(處于草案階段,暫不介紹)

    Worklets提供了唯一的方法Worklet.addModule(),這個方法用于向Worklet添加執行模塊,具體的使用方法,我們在后續的Paint API、Layout API、Animation API中介紹。

    九、Paint API

    Paint API允許開發者通過Canvas 2d的方法來繪制元素的背景、邊框、內容等圖形,這在原始的CSS規則中是無法做到的。

    Paint API需要結合上述提到的PaintWorklet一起使用,簡單來說就是開發者構建一個PaintWorklet,再將它傳入Paint API就可以繪制相應的Canvas圖形。如果你熟悉Canvas,那Paint API對你來說也不會陌生。

    使用Paint API的過程簡述如下:

    1. 使用registerPaint()方法創建一個PaintWorklet。
    2. 將它添加到Worklet模塊中,CSS.paintWorklet.addModule()。
    3. 在CSS中通過paint()方法使用它。

    圖片

    其中registerPaint()方法用于創建一個PaintWorklet,在這個方法中開發者可以利用Canvas 2d自定義圖形繪制。

    可以通過Google Chrome Labs給出的一個paint API案例checkboardWorklet來直觀看看它的具體使用方法,案例中利用Paint API為textarea繪制彩色的網格背景,它的代碼組成很簡單:

    /* checkboardWorklet.js */
    class CheckerboardPainter {
    paint(ctx, geom, properties) {
    const colors = ['red', 'green', 'blue'];
    const size = 32;
    for(let y = 0; y < geom.height/size; y++) {
    for(let x = 0; x < geom.width/size; x++) {
    const color = colors[(x + y) % colors.length];
    ctx.beginPath();
    ctx.fillStyle = color;
    ctx.rect(x * size, y * size, size, size);
    ctx.fill();
    }
    }
    }
    }
    // 注冊checkerboard
    registerPaint('checkerboard', CheckerboardPainter);
    /* index.html */
    <script>
    CSS.paintWorklet.addModule('path/to/checkboardWorklet.js') // 添加checkboardWorklet到paintWorklet
    </script>
    /* index.html */
    <!doctype html>
    <textarea></textarea>
    <style>
    textarea {
    background-image: paint(checkerboard); // 使用paint()方法調用checkboard繪制背景
    }
    </style>

    通過上述三個步驟,最終生成的textarea背景效果如圖所示:

    圖片

    感興趣的同學可以訪問 houdini-samples查看更多官方樣例。

    十、Animation API

    在過去,當我們想要對DOM元素執行動畫時,通常只有兩個選擇:CSS Transitions和CSS Animations。這兩者在使用上雖然簡單,也能滿足大部分的動畫需求,但是它們有兩個共同的缺點:

    • 僅僅依賴時間來執行動畫(time-driven):動畫的執行僅和時間有關。
    • 無狀態(stateless):開發者無法干預動畫的執行過程,獲取不到動畫執行的中間狀態。

    但是在一些場景下,我們想要開發一個非時間驅動的動畫或者想要控制動畫的執行狀態,就很難做到。比如視差滾動(Parallax Scrolling),它是根據滾動的情況來執行動畫的,并且每個元素根據滾動情況作出不一致的動畫效果,下面是個簡單的視差滾動效果示例,在通常情況下要實現更加復雜的視差滾動效果(例如beckett頁面的效果)是比較困難的。

    圖片

    Animation API卻可以幫助我們輕松做到。

    在功能方面,它是CSS Transitions和CSS Animations的擴展,它允許用戶干預動畫執行的過程,例如結合用戶的scroll、hover、click事件來控制動畫執行,像是為動畫增加了進度條,通過進度條控制動畫進程,從而實現一些更加復雜的動畫場景。

    在性能方面,它依賴于AnimationWorklet,運行在單獨的Worklet線程,因此具有更高的動畫幀率和流暢度,這在低端機型中尤為明顯(當然,通常低端機型中的瀏覽器內核還不支持該特性,這里只是說明Animation API對動畫的視覺體驗優化是很友好的)。

    Animation API的使用和Paint API一樣,也同樣遵循Worklet的創建和使用流程,分為三個步驟,簡述如下:

    1. 使用registerAnimator()方法創建一個AnimationWorklet。
    2. 將它添加到Worklet模塊中,CSS.animationWorklet.addModule()。
    3. 使用new WorkletAnimation(name, KeyframeEffect)創建和執行動畫。

    圖片

    /* myAnimationWorklet.js */
    registerAnimator("myAnimationWorklet", class {
    constructor(options) {
    /* 構造函數,動畫示例被創建時調用,可用于做一些初始化 */
    }
    //
    animate(currentTime, effect) {
    /* 干預動畫的執行 */
    }
    });
    /* index.html */
    await CSS.animationWorklet.addModule("path/to/myAnimationWorklet.js");;
    /* index.html */
    /* 傳入myAnimationWorklet,創建WorkletAnimation */
    new WorkletAnimation(
    'myAnimationWorklet', // 動畫名稱
    new KeyframeEffect( // 動畫timeline(對應于步驟一中animate(currentTime, effect)中的effect參數)
    document.querySelector('#target'),
    [
    {
    transform: 'translateX(0)'
    },
    {
    transform: 'translateX(200px)'
    }
    ],
    {
    duration: 2000, // 動畫執行時長
    iterations: Number.POSITIVE_INFINITY // 動畫執行次數
    }
    ),
    document.timeline // 控制動畫執行進程的數值(對應于步驟一中animate(currentTime, effect)中的currentTime參數)
    ).play();

    可以看到步驟一的animate(currentTime, effect)方法有兩個參數,就是它們讓開發者能夠干預動畫執行過程。

    currentTime:

    用于控制動畫執行的數值,對應于步驟3例子中傳入的document.timeline參數,通常根據它的數值來動態修改另一個參數effect,從而影響動畫執行。例如我們可以傳入document.timeline或者傳入element.scrollTop作為這個動態數值,傳入前者表明我們只是想用時間變化來控制動畫的執行,傳入后者表明我們想通過滾動距離來控制動畫執行。

    document.timeline是每個頁面被打開后從0開始遞增的時間數值,可以簡單理解為頁面被打開的時長,初始時document.timeline === 0,隨著時間不斷遞增。

    effect:

    對應于步驟3中傳入的new KeyframeEffect(),可通過修改它來影響動畫執行。一個很常見的做法是,通過修改effect.localTime控制動畫的執行,effect.localTime的作用相當于控制動畫播放的進度條,修改它的數值就相當于拖動動畫播放的進度。

    如果不修改effect.localTime或者設置effect.localTime = currentTime,那么動畫會隨著document.timeline正常勻速執行,線性動畫。但是如果將effect.localTime設置為某個固定值,例如effect.localTime = 1000ms,那么動畫將會定格在1000ms時對應的幀,不會繼續執行。

    為了更好理解effect.localTime,可以來看看effect.localTime和動畫執行之間的關系,假設我們創建了一個2000ms時長的動畫,并且動畫沒有設置delay時間。

    圖片

    通過上面的描述,大家應該get到如何做一個簡單的滾動驅動(scroll-driven)的動畫了,實際上有個專門用于生成滾動動畫的類:ScrollTimeline,它的用法也很簡單:

    /* myWorkletAnimation.js */
    new WorkletAnimation(
    'myWorkletAnimation',
    new KeyframeEffect(
    document.querySelector('#target'),
    [
    {
    transform: 'translateX(0)'
    },
    {
    transform: 'translateX(500px)'
    }
    ],
    {
    duration: 2000,
    fill: 'both'
    }
    ),
    new ScrollTimeline({
    scrollSource: document.querySelector('.scroll-area'), // 監聽的滾動元素
    orientation: "vertical", // 監聽的滾動方向"horizontal"或"vertical"
    timeRange: 2000 // 根據scroll的高度,傳入0 - timeRage之間的數值,當滾動到頂端時,傳入0,當滾動到底端時,傳入2000
    })
    ).play();

    圖片

    這樣一來,通過簡單的幾行代碼,一個簡單的滾動驅動的動畫就做好了,它比任何CSS Animations或CSS Transitions都要順暢。

    接下來再看看最后一個同樣有潛力的API:Layout API 。

    十一、Layout API

    Layout API允許用戶自定義新的布局規則,創造類似flex、grid之外的布局。

    但創建一個完備的布局規則并不簡單,官方的flex、grid布局是充分考慮了各種邊界情況,才能確保使用時不會出錯。同時Layout API使用起來也比其它API更為復雜,受限于篇幅,本文僅簡單展示相關的API和使用方式,具體細節可參考官方描述。

    Layout API和其它兩個API相似,使用步驟同樣分為三個步驟,簡述如下:

    • 通過registerLayout()創建一個LayoutWorklet。
    • 將它添加到Worklet模塊中,CSS.layoutWorklet.addModule()。
    • 通過display: layout(exampleLayout)使用它。

    圖片

    /* myLayoutWorklet.js */
    registerLayout('myLayoutWorklet', class {
    static get inputProperties() { return ['--my-prop']; }
    static get childrenInputProperties() { return ['--my-child-prop']; }
    static get layoutOptions() {
    return {
    childDisplay: 'normal',
    sizing: 'block-like'
    };
    }
    intrinsicSizes(children, edges, styleMap) {
    /* ... */
    }
    layout(children, edges, constraints, styleMap, breakToken) {
    /* ... */
    }
    });
    CSS.layoutWorklet.addModule('path/to/myLayoutWorklet.js');
    .my-layout {
    display: layout(myLayoutWorklet);
    }

    一個Google Chrome Labs案例如下所示,通過Layout API實現了一個瀑布流布局。

    圖片

    雖然通過Layout API自定義布局較為困難,但是我們依然可以引入別人的優秀開源Worklet,幫助自己實現復雜的布局。

    十二、新特性檢測

    鑒于當前Houdini APIs的瀏覽器支持度仍然不是很完美,在使用這些API時需要先做特性檢測,再考慮使用它們。

    /* 特性檢測 */
    if (CSS.paintWorklet) {
    /* ... */
    }
    if (CSS.animationWorklet) {
    /* ... */
    }
    if (CSS.layoutWorklet) {
    /* ... */
    }

    想要在chrome中調試,可以在地址欄輸入chrome://flags/#enable-experimental-web-platform-features,并勾選啟用Experimental Web Platform features。

    圖片

    十三、總結

    Houdini APIs讓開發者有辦法接觸到CSS渲染引擎,通過各種API實現更高性能和更復雜的CSS渲染效果。雖然它還沒有完全準備好,很多API甚至還處于草案階段,但它給我們帶來了更多可能性,并且諸如paint API、Typed OM、Properties & Values API這些新特性也都被廣泛支持了,可以直接用于增強我們的頁面效果。未來Houdini APIs一定會慢慢走進開發者的世界,大家可以期待并做好準備迎接它。

    參考文獻:

    1. W3C Houdini Specification Drafts
    2. State of Houdini (Chrome Dev Summit 2018)
    3. Houdini’s Animation Worklet - Google Developers
    4. Interactive Introduction to CSS Houdini
    5. CSS Houdini Experiments
    6. Interactive Introduction to CSS Houdini
    7. Houdini Samples by Google Chrome Labs
    責任編輯:未麗燕 來源: vivo互聯網技術
    相關推薦

    2010-09-14 09:18:28

    DIVCSS

    2010-09-15 15:39:03

    CSS hack

    2010-08-19 15:47:34

    CSS Reset瀏覽器

    2010-09-15 16:19:17

    IECSS hack

    2010-08-20 14:11:26

    IE火狐瀏覽器

    2010-06-23 13:24:00

    CSSCSS選擇器

    2015-06-12 11:26:02

    CSS瀏覽器 CSS Hac

    2010-09-14 14:18:09

    CSS跨瀏覽器開發

    2021-07-07 07:47:10

    瀏覽器CSS兼容

    2022-12-12 11:11:05

    2023-09-05 09:40:55

    SCSS預處理器

    2010-08-20 13:46:10

    IEFirefoxCSS

    2023-09-05 09:44:26

    CSS處理器函數

    2010-08-31 16:49:58

    2013-11-20 13:04:41

    css瀏覽器渲染

    2023-03-01 00:06:14

    JavaScrip框架CSS

    2012-02-29 09:27:36

    ibmdw

    2010-04-01 13:03:10

    2010-09-16 13:48:15

    CSS Hack

    2013-03-12 10:01:21

    WebCSSJS
    點贊
    收藏

    51CTO技術棧公眾號

    亚洲中文字幕无码一区二区三区| 影音先锋欧美精品| 日韩不卡一二区| 国产xxxx在线观看| 在线观看一区| 日韩精品999| 高清一区在线观看| huan性巨大欧美| 岛国av在线一区| 欧美中文字幕视频| 午夜激情视频在线播放| 这里视频有精品| 欧美午夜宅男影院在线观看| 亚洲精品在线观看免费| 超碰人人人人人人| 久色成人在线| 欧美精品生活片| 日韩网站在线播放| 国产高清日韩| 狠狠躁夜夜躁久久躁别揉| 先锋影音一区二区三区| 中文无码av一区二区三区| 亚洲最大黄网| 亚洲精品影视在线观看| 亚洲精品中文字幕乱码无线| 国产三级电影在线播放| 国产精品电影院| 激情视频一区二区| 99久久久国产精品无码网爆| 亚洲尤物影院| 精品中文字幕乱| 亚洲女优在线观看| 精品人人人人| 欧美一区二区三区播放老司机| 久久久免费视频网站| 欧美xxxx性xxxxx高清| 中文字幕免费一区| 麻豆精品蜜桃一区二区三区| 粉嫩av一区二区夜夜嗨| 麻豆一区二区在线| 欧美在线一区二区视频| 粉嫩aⅴ一区二区三区| 91蜜臀精品国产自偷在线| 亚洲欧美日韩精品久久亚洲区 | 美女任你摸久久 | 亚洲综合av一区二区三区| 亚洲不卡av一区二区三区| 97av中文字幕| 黄色网页在线免费观看| 亚洲欧美一区二区视频| 亚洲欧美99| av成人手机在线| 国产片一区二区| 日韩电影天堂视频一区二区| 欧洲成人av| 久久亚洲综合色| 九九九九精品九九九九| 天天色天天操天天射| 不卡免费追剧大全电视剧网站| 99免费在线观看视频| 99草在线视频| 国产精品一二三| 国产精品一区专区欧美日韩| 中文字幕人妻丝袜乱一区三区 | 国产福利在线免费| 日韩三级一区| 欧美一区二区成人| 麻豆精品国产传媒| 成人春色在线观看免费网站| 精品伦理精品一区| 欧美xxxx×黑人性爽| 黄色免费大全亚洲| 亚洲精品中文字幕av| 欧美裸身视频免费观看| 久久久久久亚洲中文字幕无码| 亚洲区小说区| 在线日韩日本国产亚洲| 日本成人免费在线观看| 亚洲一区二区| 久久男人资源视频| 六月丁香婷婷综合| 日韩av一区二区三区| 成人精品视频在线| 亚洲美女性生活| 26uuu色噜噜精品一区| 日韩av一区二区三区在线| 一级毛片视频在线观看| 亚洲精品国产a| 男人日女人逼逼| 最新欧美电影| 日韩三级视频中文字幕| 精品国产av色一区二区深夜久久| 伊人久久大香线蕉综合网蜜芽| 深夜福利亚洲导航| 久久久久久蜜桃| 日韩在线一二三区| 96精品久久久久中文字幕| 神马一区二区三区| 中文字幕va一区二区三区| 久久观看最新视频| jk漫画禁漫成人入口| 欧美日韩久久久久久| 精品人妻二区中文字幕| 亚洲欧美tv| 欧美日韩国产二区| 无码免费一区二区三区| 国产成人三级在线观看| 麻豆91蜜桃| 爆操欧美美女| 日韩精品欧美在线| 国产99久久久久久免费看| 蜜臀av性久久久久蜜臀aⅴ| 999热视频| 国产综合在线观看| 亚洲一区二区三区自拍| mm131国产精品| 日本在线中文字幕一区| 久久亚洲影音av资源网| 成人看片黄a免费看在线| 国产精品一区二区三区免费视频 | 欧美视频不卡中文| 亚洲在线观看网站| 欧洲免费在线视频| 国产精品日韩精品欧美在线| 夜夜添无码一区二区三区| 欧美爱爱视频| 亚洲欧美中文日韩在线v日本| 久久久国产精品人人片| 精品一区二区三区免费视频| 欧美性bbwbbwbbwhd| h片视频在线观看| 欧美一区二区播放| 日韩中文在线| 亚洲成a人片在线观看中文| 久久久精品麻豆| 国产亚洲精品美女久久| 久久精品成人动漫| 国产精品久久久久久人| 成人激情免费网站| 国产影视一区二区| kk眼镜猥琐国模调教系列一区二区| 日韩精品久久久| 草草在线视频| 日韩欧美二区三区| 美国一级片在线观看| 久久精品日产第一区二区| 国产日韩一区欧美| 日韩三级免费| 日韩片之四级片| 亚洲综合视频网站| 激情综合色播激情啊| 亚洲精品一区二区三区四区五区 | 欧美18xxxx| 欧美激情亚洲激情| 国产77777| 亚洲一级电影视频| 911亚洲精选| 国内精品久久久久久久影视蜜臀| 91精品国产高清久久久久久91裸体| 日本不卡三区| 欧美一区二区三区男人的天堂| 五月综合色婷婷| 国产毛片精品视频| 国产激情在线看| 加勒比色综合久久久久久久久| 午夜精品久久久久久久白皮肤| 婷婷丁香花五月天| 色综合天天综合网国产成人综合天| 中文字幕在线看高清电影| 日韩高清一区在线| 综合视频在线观看| 综合激情网...| 久久久久成人精品| 性xxxxbbbb| 欧美在线一区二区三区| 国产日韩精品中文字无码| 国产精品一区久久久久| 久久99国产精品一区| 爱高潮www亚洲精品| 欧美一区二区三区免费观看| 国产日本在线观看| 欧美乱妇15p| 国产在线拍揄自揄拍| 久久夜色精品一区| 污污视频网站在线| 亚洲区第一页| 日韩色妇久久av| 日韩精品一区二区三区免费视频| 91产国在线观看动作片喷水| av片在线免费观看| 精品日韩欧美在线| 天天射天天干天天| 亚洲女人****多毛耸耸8| 香港三日本8a三级少妇三级99| 日韩精品一二三四| 国产尤物av一区二区三区| 一本久久青青| 亚洲最大的av网站| 综合久久2023| 九九九热精品免费视频观看网站| 亚洲色图21p| 91精品国产美女浴室洗澡无遮挡| www欧美在线| 尤物在线观看一区| 国产视频三区四区| 99re亚洲国产精品| 美女被艹视频网站| 日本欧美一区二区| 97超碰国产精品| 日韩片欧美片| 加勒比在线一区二区三区观看| 欧美成人家庭影院| 国产97在线播放| 成入视频在线观看| 欧美成人性生活| 91ph在线| 亚洲人成电影网站色…| 欧美一级在线免费观看| 91精品欧美综合在线观看最新| 91精品国产乱码在线观看| 中文字幕综合网| 日本理论中文字幕| 99国产精品久久久久| 国产sm在线观看| 久久99最新地址| 少妇性l交大片| 9国产精品视频| 韩国无码av片在线观看网站| 欧美aaaaaaaaaaaa| 日韩色妇久久av| 久久综合欧美| 欧美福利精品| 日韩黄色网络| 国产精品综合久久久久久| 麻豆视频久久| 91九色视频导航| 欧美黑粗硬大| 国产精品视频中文字幕91| 日韩网站中文字幕| 国产不卡视频在线| 欧美性猛交xxx高清大费中文| 国内精品模特av私拍在线观看| 欧美家庭影院| 欧美精品video| 久草在线视频资源| 欧美激情a∨在线视频播放| aaa大片在线观看| 久久精视频免费在线久久完整在线看| 无遮挡动作视频在线观看免费入口| 亚洲性xxxx| 第一页在线观看| 最新国产成人av网站网址麻豆| 97超碰人人在线| 中文字幕亚洲欧美日韩2019| wwwxxx在线观看| 中文字幕亚洲字幕| 日本不卡三区| 欧美风情在线观看| 国产在线88av| 日本亚洲欧洲色| av在线不卡精品| 成人黄色在线播放| 久久丁香四色| 国产精品一区在线播放| 亚洲三级性片| 少妇免费毛片久久久久久久久 | 国产美女精品一区二区三区| 欧美专区第二页| 成人激情综合网站| 亚洲天堂久久新| 亚洲国产精品黑人久久久| 国产福利在线导航| 亚洲精品国产一区二区三区四区在线 | 亚洲一区二区激情| 日韩一区二区三区在线观看| 欧美一区二区公司| 亚洲日本欧美中文幕| 日p在线观看| 久久久久久久亚洲精品| 中文字幕成在线观看| 国产精品自拍视频| 综合欧美亚洲| 奇米精品在线| 亚洲最大av| 国产偷人视频免费| 韩国三级电影一区二区| 中文成人无字幕乱码精品区| 中文字幕免费一区| 日韩激情在线播放| 欧美伊人久久大香线蕉综合69 | 18岁成人毛片| 黑人精品xxx一区一二区| 最新黄色网址在线观看| 日韩欧美一区二区免费| 青青草免费在线| 久久亚洲影音av资源网| 免费成人动漫| 2022国产精品| 国精一区二区| 国产日韩亚洲欧美在线| 日本vs亚洲vs韩国一区三区二区| 国产人妻精品午夜福利免费| 国产日本欧美一区二区| 精品少妇theporn| 欧美日韩美女一区二区| 五月激情婷婷网| 毛片精品免费在线观看| 精品国模一区二区三区| 精品欧美一区二区三区久久久| 羞羞色午夜精品一区二区三区| 浮妇高潮喷白浆视频| 国产精品一区一区三区| 91禁男男在线观看| 欧美日韩亚洲一区二| 性中国xxx极品hd| 日韩在线欧美在线| 成人免费直播| 国产欧美一区二区视频| 综合激情视频| 日本中文字幕观看| 日本一区二区免费在线| 免费观看成人毛片| 亚洲电影免费观看| av在线免费网站| 国产欧美精品一区二区三区-老狼| 亚洲+变态+欧美+另类+精品| 青草网在线观看| 国产精品一区2区| 成人18视频免费69| 欧美中文字幕一二三区视频| 久久av少妇| 欧美一级高清免费播放| 国产精品chinese在线观看| 在线不卡日本| 久久av中文字幕片| 91n在线视频| 欧美午夜片在线观看| 毛片在线播放网站| 日本中文字幕久久看| 亚洲视频分类| 97在线免费公开视频| 91视频在线观看免费| 亚欧视频在线观看| 亚洲护士老师的毛茸茸最新章节 | 青青草国产免费一区二区下载| 国产精品亚洲αv天堂无码| 99久久久精品免费观看国产蜜| 青青草手机视频在线观看| 欧美一区二区三区思思人| 中文在线观看免费| 99精彩视频| 国产精品草草| 亚洲一区二区在线免费| 福利一区福利二区微拍刺激| 水中色av综合| 国产福利精品av综合导导航| 欧美日韩国产一区二区三区不卡| 美女网站免费观看视频| 国产精品理论片| 国产男女猛烈无遮挡| 九色成人免费视频| 玖玖玖免费嫩草在线影院一区| 69堂免费视频| 国产日韩一级二级三级| 91肉色超薄丝袜脚交一区二区| 久久久999国产| 77成人影视| 中文字幕乱码人妻综合二区三区 | 国产三级国产精品国产国在线观看| 91精品在线免费观看| 欧美人动性xxxxz0oz| 久久青青草综合| 日本在线播放一区二区三区| 国语对白在线播放| 亚洲精品www久久久| 日韩av首页| 成人在线视频一区二区三区| 成人avav影音| 精品乱码一区内射人妻无码| 久久九九国产精品怡红院| av不卡一区二区| 激情网站五月天| 亚洲女同一区二区| 亚洲 精品 综合 精品 自拍| 国产精品网红直播| 国产精品大片| 美女av免费看| 精品国产精品一区二区夜夜嗨| 成人免费看黄| 成人在线观看www| 97久久超碰国产精品| 夜夜狠狠擅视频| 国内伊人久久久久久网站视频| 精品视频日韩| 国产亚洲精品成人a| 欧美在线观看一区| 国产精品69xx| 一区二区日本| 久久综合网色—综合色88| 99视频在线观看免费|