整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          聽說過CSS in JS,那你聽說過JS in CSS嗎


          SS in JS

          CSS in JS是一種解決css問題想法的集合,而不是一個(gè)指定的庫。從CSS in JS的字面意思可以看出,它是將css樣式寫在JavaScript文件中,而不需要獨(dú)立出.css.less之類的文件。將css放在js中使我們更方便的使用js的變量模塊化tree-shaking。還解決了css中的一些問題,譬如:更方便解決基于狀態(tài)的樣式更容易追溯依賴關(guān)系生成唯一的選擇器來鎖定作用域。盡管CSS in JS不是一個(gè)很新的技術(shù),但國內(nèi)的普及程度并不高。由于Vue和Angular都有屬于他們自己的一套定義樣式的方案,React本身也沒有管用戶怎樣定義組件的樣式[1],所以CSS in JS在React社區(qū)的熱度比較高。

          目前為止實(shí)現(xiàn)CSS in JS的第三方庫有很多:(http://michelebertoli.github.io/css-in-js/)。像JSS[2]styled-components[3]等。在這里我們就不展開贅述了(相關(guān)鏈接已放在下方),這篇文章的重點(diǎn)是JS in CSS

          JS in CSS又是什么

          在上面我們提到CSS in JS就是把CSS寫在JavaScript中,那么JS in CSS我們可以推斷出就是可以在CSS中使用JavaScript腳本,如下所示。可以在CSS中編寫Paint API的功能。還可以訪問:ctx,geom。甚至我們還可以編寫自己的css自定義屬性等。這些功能的實(shí)現(xiàn)都基于CSS Houdini[4]

          .el {
            --color: cyan;
            --multiplier: 0.24;
            --pad: 30;
            --slant: 20;
            --background-canvas: (ctx, geom) => {
              let multiplier = var(--multiplier);
              let c = `var(--color)`;
              let pad = var(--pad);
              let slant = var(--slant);
          
              ctx.moveTo(0, 0);
              ctx.lineTo(pad + (geom.width - slant - pad) * multiplier, 0);
              ctx.lineTo(pad + (geom.width - slant - pad) * multiplier + slant, geom.height);
              ctx.lineTo(0, geom.height);
              ctx.fillStyle = c;
              ctx.fill();
            };
            background: paint(background-canvas);
            transition: --multiplier .4s;
          }
          .el:hover {
            --multiplier: 1;
          }
          

          Houdini 解決了什么問題

          CSS 與 JS的標(biāo)準(zhǔn)制定流程對(duì)比

          在如今的Web開發(fā)中,JavaScript幾乎占據(jù)了項(xiàng)目代碼的大部分。我們可以在項(xiàng)目開發(fā)中使用ES 2020、ES2021、甚至提案中的新特性(如:Decorator[5]),即使瀏覽器尚未支持,也可以編寫Polyfill或使用Babel之類的工具進(jìn)行轉(zhuǎn)譯,讓我們可以將最新的特性應(yīng)用到生產(chǎn)環(huán)境中(如下圖所示)。

          JavaScript標(biāo)準(zhǔn)制定流程.png

          而CSS就不同了,除了制定CSS標(biāo)準(zhǔn)規(guī)范所需的時(shí)間外,各家瀏覽器的版本、實(shí)戰(zhàn)進(jìn)度差異更是曠日持久(如下圖所示),最多利用PostCSS、Sass等工具來幫我們轉(zhuǎn)譯出瀏覽器能接受的CSS。開發(fā)者們能操作的就是通過JS去控制DOMCSSOM來影響頁面的變化,但是對(duì)于接下來的LayoutPaintComposite就幾乎沒有控制權(quán)了。為了解決上述問題,為了讓CSS的魔力不在受到瀏覽器的限制,Houdini就此誕生。

          CSS 標(biāo)準(zhǔn)制定流程.png

          CSS Polyfill

          我們上文中提到JavaScript中進(jìn)入提案中的特性我們可以編寫Polyfill,只需要很短的時(shí)間就可以講新特性投入到生產(chǎn)環(huán)境中。這時(shí),腦海中閃現(xiàn)出的第一個(gè)想法就是CSS Polyfill,只要CSS的Polyfill 足夠強(qiáng)大,CSS或許也能有JavaScript一樣的發(fā)展速度,令人可悲的是編寫CSS Polyfill異常的困難,并且大多數(shù)情況下無法在不破壞性能的情況下進(jìn)行。這是因?yàn)镴avaScript是一門動(dòng)態(tài)腳本語言[6]。它帶來了極強(qiáng)的擴(kuò)展性,正是因?yàn)檫@樣,我們可以很輕松使用JavaScript做出JavaScript的Polyfill。但是CSS不是動(dòng)態(tài)的,在某些場景下,我們可以在編譯時(shí)將一種形式的CSS的轉(zhuǎn)換成另一種(如PostCSS[7])。如果你的Polyfill依賴于DOM結(jié)構(gòu)或者某一個(gè)元素的布局、定位等,那么我們的Polyfill就無法編譯時(shí)執(zhí)行,而需要在瀏覽器中運(yùn)行了。不幸的是,在瀏覽器中實(shí)現(xiàn)這種方案非常不容易。

          頁面渲染流程.png

          如上圖所示,是從瀏覽器獲取到HTML到渲染在屏幕上的全過程,我們可以看到只有帶顏色(粉色、藍(lán)色)的部分是JavaScript可以控制的環(huán)節(jié)。首先我們根本無法控制瀏覽器解析HTML與CSS并將其轉(zhuǎn)化為DOMCSSOM的過程,以及Cascade,Layout,Paint,Composite我們也無能為力。整個(gè)過程中我們唯一完全可控制的就是DOM,另外CSSOM部分可控。

          CSS Houdini草案中提到,這種程度的暴露是不確定的、兼容性不穩(wěn)定的以及缺乏對(duì)關(guān)鍵特性的支持的。比如,在瀏覽器中的 CSSOM 是不會(huì)告訴我們它是如何處理跨域的樣式表,而且對(duì)于瀏覽器無法解析的 CSS 語句它的處理方式就是不解析了,也就是說——如果我們要用 CSS polyfill讓瀏覽器去支持它尚且不支持的屬性,那就不能在 CSSOM 這個(gè)環(huán)節(jié)做,我們只能遍歷一遍DOM,找到 <style><link rel="stylesheet"> 標(biāo)簽,獲取其中的 CSS 樣式、解析、重寫,最后再加回 DOM 樹中。令人尷尬的是,這樣DOM樹全部刷新了,會(huì)導(dǎo)致頁面的重新渲染(如下如所示)。

          即便如此,有的人可能會(huì)說:“除了這種方法,我們也別無選擇,更何況對(duì)網(wǎng)站的性能也不會(huì)造成很大的影響”。那么對(duì)于部分網(wǎng)站是這樣的。但如果我們的Polyfill是需要對(duì)可交互的頁面呢?例如scrollresizemousemovekeyup等等,這些事件隨時(shí)會(huì)被觸發(fā),那么意味著隨時(shí)都會(huì)導(dǎo)致頁面的重新渲染,交互不會(huì)像原本那樣絲滑,甚至導(dǎo)致頁面崩潰,對(duì)用戶的體驗(yàn)也極其不好。

          綜上所述,如果我們想讓瀏覽器解析它不認(rèn)識(shí)的樣式(低版本瀏覽器使用grid布局),然而渲染流程我們無法介入,我們也只能通過手動(dòng)更新DOM的方式,這樣會(huì)帶來很多問題,Houdini的出現(xiàn)正是致力于解決他們。

          Houdini API

          Houdini是一組底層API,它公開了CSS引擎的各個(gè)部分,如下圖所示展示了每個(gè)環(huán)節(jié)對(duì)應(yīng)的新API(灰色部分各大瀏覽器還未實(shí)現(xiàn)),從而使開發(fā)人員能夠通過加入瀏覽器渲染引擎的樣式和布局過程來擴(kuò)展CSS。Houdini是一群來自Mozilla,Apple,Opera,Microsoft,HP,Intel和Google的工程師組成的工作小組設(shè)計(jì)而成的。它們使開發(fā)者可以直接訪問CSS對(duì)象模型(CSSOM),使開發(fā)人員可以編寫瀏覽器可以解析為CSS的代碼,從而創(chuàng)建新的CSS功能,而無需等待它們?cè)跒g覽器中本地實(shí)現(xiàn)。

          CSS Houdini-API

          Properties & Values API

          盡管當(dāng)前已經(jīng)有了CSS變量,可以讓開發(fā)者控制屬性值,但是無法約束類型或者更嚴(yán)格的定義,CSS Houdini新的API,我們可以擴(kuò)展css的變量,我們可以定義CSS變量的類型,初始值,繼承。它是css變量更強(qiáng)大靈活。

          CSS變量現(xiàn)狀:

          .dom {
            --my-color: green;
            --my-color: url('not-a-color'); // 它并不知道當(dāng)前的變量類型
            color: var(--my-color);
          }
          
          

          Houdini提供了兩種自定義屬性的注冊(cè)方式,分別是在js和css中。

          CSS.registerProperty({
            name: '--my-prop', // String 自定義屬性名
            syntax: '<color>', // String 如何去解析當(dāng)前的屬性,即屬性類型,默認(rèn) *
            inherits: false, // Boolean 如果是true,子節(jié)點(diǎn)將會(huì)繼承
            initialValue: '#c0ffee', // String 屬性點(diǎn)初始值
          });
          

          我們還可以在css中注冊(cè),也可以達(dá)到上面的效果

          @property --my-prop {
            syntax: '<color>';
            inherits: false;
            initial-value: #c0ffee;
          }
          

          這個(gè)API中最令人振奮人心的功能是自定義屬性上添加動(dòng)畫,像這樣:transition: --multiplier 0.4s;,這個(gè)功能我們?cè)谇懊娼榻B什么是js in css那個(gè)demo[8]用使用過。我們還可以使用+使syntax屬性支持一個(gè)或多個(gè)類型,也可以使用|來分割。更多syntax屬性值:

          屬性值描述<length>長度值<number>數(shù)字<percentage>百分比<length-percentage>長度或百分比,calc將長度和百分比組成的表達(dá)式<color>顏色<image>圖像<url>網(wǎng)址<integer>整數(shù)<angle>角度<time>時(shí)間<resolution>分辨率<transform-list>轉(zhuǎn)換函數(shù)<custom-ident>ident

          Worklets

          Worklets是渲染引擎的擴(kuò)展,從概念上來講它類似于Web Workers[9],但有幾個(gè)重要的區(qū)別:

          1. 設(shè)計(jì)為并行,每個(gè)Worklets必須始終有兩個(gè)或更多的實(shí)例,它們中的任何一個(gè)都可以在被調(diào)用時(shí)運(yùn)行
          2. 作用域較小,限制不能訪問全局作用域的API(Worklet的函數(shù)除外)
          3. 渲染引擎會(huì)在需要的時(shí)候調(diào)用他們,而不是我們手動(dòng)調(diào)用

          Worklet是一個(gè)JavaScript模塊,通過調(diào)用worklet的addModule方法(它是個(gè)Promise)來添加。比如registerLayout,registerPaint, registerAnimator 我們都需要放在Worklet中

          //加載單個(gè)
          await demoWorklet.addModule('path/to/script.js');
          
          // 一次性加載多個(gè)worklet
          Promise.all([
            demoWorklet1.addModule('script1.js'),
            demoWorklet2.addModule('script2.js'),
          ]).then(results => {});
          
          registerDemoWorklet('name', class {
          
            // 每個(gè)Worklet可以定義要使用的不同函數(shù)
            // 他們將由渲染引擎在需要時(shí)調(diào)用
            process(arg) {
              return !arg;
            }
          });
          
          

          Worklets的生命周期

          Worklets lifecycle

          1. Worklet的生命周期從渲染引擎內(nèi)開始
          2. 對(duì)于JavaScript,渲染引擎啟動(dòng)JavaScript主線程
          3. 然后他將啟動(dòng)多個(gè)worklet進(jìn)程,并且可以運(yùn)行。這些進(jìn)程理想情況下是獨(dú)立于主線程的線程,這樣就不會(huì)阻塞主線程(但它們也不需要阻塞)
          4. 然后在主線程中加載我們?yōu)g覽器的JavaScript
          5. 該JavaScript調(diào)用 worklet.addModule 并異步加載一個(gè)worklet
          6. 加載后,將worklet加載到兩個(gè)或多個(gè)可用的worklet流程中
          7. 當(dāng)需要時(shí),渲染引擎將通過從加載的Worklet中調(diào)用適當(dāng)?shù)奶幚砗瘮?shù)來執(zhí)行Worklet。該調(diào)用可以針對(duì)任何并行的Worklet實(shí)例。

          Typed OM

          Typed OM是對(duì)現(xiàn)有的CSSOM的擴(kuò)展,并實(shí)現(xiàn) Parsing APIProperties & Values API相關(guān)的特性。它將css值轉(zhuǎn)化為有意義類型的JavaScript的對(duì)象,而不是像現(xiàn)在的字符串。如果我們嘗試將字符串類型的值轉(zhuǎn)化為有意義的類型并返回可能會(huì)有很大的性能開銷,因此這個(gè)API可以讓我們更高效的使用CSS的值。

          現(xiàn)在讀取CSS值增加了新的基類CSSStyleValue,他有許多的子類可以更加精準(zhǔn)的描述css值的類型:

          子類描述CSSKeywordValueCSS關(guān)鍵字和其他標(biāo)識(shí)符(如inherit或grid)CSSPositionValue位置信息 (x,y)CSSImageValue表示圖像的值屬性的對(duì)象CSSUnitValue表示為具有單個(gè)單位的單個(gè)值(例如50px),也可以表示為沒有單位的單個(gè)值或百分比CSSMathValue比較復(fù)雜的數(shù)值,比如有calc,min和max。這包括子類 CSSMathSum, CSSMathProduct, CSSMathMin,CSSMathMax, CSSMathNegate 和 CSSMathInvertCSSTransformValue由CSS transforms組成的CSSTransformComponent列表,其中包括CSSTranslate, CSSRotate, CSSScale, CSSSkew, CSSSkewX, CSSSkewY, CSSPerspective 和 CSSMatrixComponent

          使用Typed OM主要有兩種方法:

          1. 通過attributeStyleMap設(shè)置和獲取有類型的行間樣式
          2. 通過computedStyleMap獲取元素完整的Typed OM樣式

          使用attributeStyleMap設(shè)置并獲取

          myElement.attributeStyleMap.set('font-size', CSS.em(2));
          myElement.attributeStyleMap.get('font-size'); // CSSUnitValue { value: 2, unit: 'em' }
          
          myElement.attributeStyleMap.set('opacity', CSS.number(.5));
          myElement.attributeStyleMap.get('opacity'); // CSSUnitValue { value: 0.5, unit: 'number' };
          

          在線demo[10]

          使用computedStyleMap

          .foo {
            transform: translateX(1em) rotate(50deg) skewX(10deg);
            vertical-align: baseline;
            width: calc(100% - 3em);
          }
          
          const cs = document.querySelector('.foo').computedStyleMap();
          
          cs.get('vertical-align');
          // CSSKeywordValue {
          //  value: 'baseline',
          // }
          
          cs.get('width');
          // CSSMathSum {
          //   operator: 'sum',
          //   length: 2,
          //   values: CSSNumericArray {
          //     0: CSSUnitValue { value: -90, unit: 'px' },
          //     1: CSSUnitValue { value: 100, unit: 'percent' },
          //   },
          // }
          
          cs.get('transform');
          // CSSTransformValue {
          //   is2d: true,
          //   length: 3,
          //   0: CSSTranslate {
          //     is2d: true,
          //     x: CSSUnitValue { value: 20, unit: 'px' },
          //     y: CSSUnitValue { value: 0, unit: 'px' },
          //     z: CSSUnitValue { value: 0, unit: 'px' },
          //   },
          //   1: CSSRotate {...},
          //   2: CSSSkewX {...},
          // }
          

          Layout API

          開發(fā)者可以通過這個(gè)API實(shí)現(xiàn)自己的布局算法,我們可以像原生css一樣使用我們自定義的布局(像display:flex, display:table)。在Masonry layout library[11] 上我們可以看到開發(fā)者們是有多想實(shí)現(xiàn)各種各樣的復(fù)雜布局,其中一些布局光靠 CSS 是不行的。雖然這些布局會(huì)讓人耳目一新印象深刻,但是它們的頁面性能往往都很差,在一些低端設(shè)備上性能問題猶為明顯。

          CSS Layout API 暴露了一個(gè)registerLayout方法給開發(fā)者,接收一個(gè)布局名(layout name)作為后面在 CSS中使用的屬性值,還有一個(gè)包含有這個(gè)布局邏輯的JavaScript類。

          my-div {
            display: layout(my-layout);
          }
          
          // layout-worklet.js
          registerLayout('my-layout', class {
            static get inputProperties() { return ['--foo']; }
            
            static get childrenInputProperties() { return ['--bar']; }
            
            async intrinsicSizes(children, edges, styleMap) {}
          
            async layout(children, edges, constraints, styleMap) {}
          });
          
          await CSS.layoutWorklet.addModule('layout-worklet.js');
          

          目前瀏覽器大部分還不支持

          Painting API

          我們可以在CSS background-image中使用它,我們可以使用Canvas 2d上下文,根據(jù)元素的大小控制圖像,還可以使用自定義屬性。

          await CSS.paintWorklet.addModule('paint-worklet.js');
          
          registerPaint('sample-paint', class {
            static get inputProperties() { return ['--foo']; }
          
            static get inputArguments() { return ['<color>']; }
          
            static get contextOptions() { return {alpha: true}; }
          
            paint(ctx, size, props, args) { }
          });
          

          Animation API

          這個(gè)API讓我們可以控制基于用戶輸入的關(guān)鍵幀動(dòng)畫,并且以非阻塞的方式。還能更改一個(gè) DOM 元素的屬性,不過是不會(huì)引起渲染引擎重新計(jì)算布局或者樣式的屬性,比如 transform、opacity 或者滾動(dòng)條位置(scroll offset)。Animation API的使用方式與 Paint APILayout API略有不同我們還需要通過new一個(gè)WorkletAnimation來注冊(cè)worklet。

          // animation-worklet.js
          registerAnimator('sample-animator', class {
            constructor(options) {
            }
            animate(currentTime, effect) {
              effect.localTime = currentTime;
            }
          });
          
          await CSS.animationWorklet.addModule('animation-worklet.js');
          
          // 需要添加動(dòng)畫的元素
          const elem = document.querySelector('#my-elem');
          const scrollSource = document.scrollingElement;
          const timeRange = 1000;
          const scrollTimeline = new ScrollTimeline({
            scrollSource,
            timeRange,
          });
          
          const effectKeyframes = new KeyframeEffect(
            elem,
            // 動(dòng)畫需要綁定的關(guān)鍵幀
            [
              {transform: 'scale(1)'},
              {transform: 'scale(.25)'},
              {transform: 'scale(1)'}
            ],
            {
              duration: timeRange,
            },
          );
          new WorkletAnimation(
            'sample-animator',
            effectKeyframes,
            scrollTimeline,
            {},
          ).play();
          

          關(guān)于此API的更多內(nèi)容:(https://github.com/w3c/css-houdini-drafts/tree/main/css-animation-worklet-1)

          Parser API

          允許開發(fā)者自由擴(kuò)展 CSS 詞法分析器。

          解析規(guī)則:

          const background = window.cssParse.rule("background: green");
          console.log(background.styleMap.get("background").value) // "green"
          
          const styles = window.cssParse.ruleSet(".foo { background: green; margin: 5px; }");
          console.log(styles.length) // 5
          console.log(styles[0].styleMap.get("margin-top").value) // 5
          console.log(styles[0].styleMap.get("margin-top").type) // "px"
          

          解析CSS:

          const style = fetch("style.css")
                  .then(response => CSS.parseStylesheet(response.body));
          style.then(console.log);
          

          Font Metrics API

          它將提供一些方法來測量在屏幕上呈現(xiàn)的文本元素的尺寸,將允許開發(fā)者控制文本元素在屏幕上呈現(xiàn)的方式。使用當(dāng)前功能很難或無法測量這些值,因此該API將使開發(fā)者可以更輕松地創(chuàng)建與文本和字體相關(guān)的CSS特性。例如:

          • flex布局: align-items baseline特性。需要知道每一個(gè)flex盒子中第一個(gè)元素的基線位置。
          • 首字母: 需要知道每個(gè)字母的基線高度和字母最大的高度,以及換行內(nèi)容的基線長度。
          • 單個(gè)字形的前進(jìn)和后退。
          • 換行: 需要訪問字體數(shù)據(jù),文本的所有樣式輸入以及布局信息(可用的段落長度等)。
          • 元素中的每一個(gè)line boxes都需要一個(gè)基線。(line boxes代表包含眾多inline boxes的這行)

          Houdini 目前進(jìn)展

          Is Houdini ready yet

          (https://ishoudinireadyyet.com/)

          Houdini 的藍(lán)圖

          了解到這里,部分開發(fā)者可能會(huì)說:“我不需要這些花里胡哨的技術(shù),并不能帶收益。我只想簡簡單單的寫幾個(gè)頁面,做做普通的Web App,并不想試圖干預(yù)瀏覽器的渲染過程從而實(shí)現(xiàn)一些實(shí)驗(yàn)性或炫酷的功能。”如果這樣想的話,我們不妨退一步再去思考。回憶下最近做過的項(xiàng)目,用于實(shí)現(xiàn)頁面效果所使用到的技術(shù),grid布局方式在考慮兼容老版本瀏覽器時(shí)也不得不放棄。我們想控制瀏覽器渲染頁面的過程并不是僅僅為了炫技,更多的是為了幫助開發(fā)者們解決以下兩個(gè)問題:

          1. 統(tǒng)一各大瀏覽器的行為
          2. JavaScript一樣,在推出新的特性時(shí),我們可以通過Polyfill的形式快速的投入生產(chǎn)環(huán)境中。

          幾年過后再回眸,當(dāng)主流瀏覽器完全支持Houdini的時(shí)候。我們可以在瀏覽器上隨心所欲的使用任何CSS屬性,并且他們都能完美支持。像今天的grid布局在舊版本瀏覽器支持的并不友好的這類問題,那時(shí)我們只需要安裝對(duì)應(yīng)的Polyfill就能解決類似的問題。

          項(xiàng)目中,我們有時(shí)需要封裝一些組件,為了高內(nèi)聚,我們有時(shí)可以把文件都寫法一個(gè)js文件中。vite官方給我們提供了jsx寫法插件 @vitejs/plugin-vue-jsx,并且可以隨便找個(gè)css in js 庫,這里我用@styils/vue

          使用插件步驟:

          npm i @vitejs/plugin-vue-jsx 
          npm i @styils/vue
          //vite.config.js
          // 引入jsx官方插件
          import vueJsx from '@vitejs/plugin-vue-jsx'
          export default defineConfig({
            plugins: [
              //更多配置看github
            	vueJsx({
                include:/\.[jt]s$/ 
              })
            ]
          })

          示例:

          // App.vue
          <template>
            <div>
              <el-button @click="Alerst" type="primary">彈框</el-button>
            </div>
          </template>
          <script setup>
          import { alert } from './utils/Alert'
          const messageProp = '彈出框數(shù)據(jù)'
          const Alerst = () => {
            alert(messageProp)
          }
          </script>
          // src/utils/Alert/index.js
          import { render } from 'vue'
          import { styled } from '@styils/vue';
          export const alert = (propsData) => {
            //創(chuàng)建一個(gè)有樣式div
            const Mydiv = styled('div', {
              position: "fixed",
              top: '50%',
              left: '50%',
              transform: 'translate(-50%,-50%)',
            })
              const container = document.createElement('div')
              document.body.appendChild(container)
              const VNode = (
                <Mydiv>   //替換原來沒有樣式的<div>
                  <h1>登錄成功</h1>
                    <p>{propsData}</p>
                    <button onClick={() => {  
                      container.remove();  
                    }}>關(guān)閉</button>
                </Mydiv> 
              )
            render(VNode, container)
          }

          效果

          個(gè)春節(jié)剛剛過去,現(xiàn)在我們用所學(xué)的知識(shí)來制作一個(gè)小案列,利用html+css+js來制作一個(gè)距離下一個(gè)春節(jié)還有多少天。

          首先我們還是準(zhǔn)備代碼編輯器:

          給大家介紹我個(gè)人比較喜歡的2款免費(fèi)代碼編輯器

          這款為國產(chǎn)代碼編輯器 HBuilder

          此款為國外好用的Sublime編輯器 同樣免費(fèi)

          進(jìn)入正題

          分別建立三個(gè)文件 html css js

          第一個(gè)是html結(jié)構(gòu)文件:命名為 index.html 代表首頁。

          第二個(gè)是css樣式文件:命名為wp_style.css和pc_style.css,兩個(gè)文件,因?yàn)樯婕暗诫娔X端運(yùn)行和手機(jī)端運(yùn)行。

          第三個(gè)是js也就是JavaScript邏輯文件:命名為script.js。


          直接上代碼:

          這是html頁面的代碼

          <!DOCTYPE html>
            <head>
              <meta charset="UTF-8">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <meta http-equiv="X-UA-Compatible" content="ie=edge">
              <link rel="stylesheet" href="css/wp_style.css">
              <link rel="stylesheet" href="css/pc_style.css">
              <title>2023年春節(jié)倒計(jì)時(shí)</title>
            </head>
            <body>
              <div class="container">
                <h2><span id="title">癸卯兔年</span>距離2023年春節(jié)還有</h2>
          	  <!-- <h3><span id="title">距離下一年春節(jié)還有</span></h3> -->
                <div class="countdown">
                  <div id="day">--</div>
                  <div id="hour">--</div>
                  <div id="minute">--</div>
                  <div id="second">--</div>
                </div>
              </div>
                <script  src="js/script.js"></script>
            </body>
          </html>

          這是css樣式的代碼:電腦端

          @media screen and (max-width: 1025px) {
          	* {
          		margin: 0;
          		padding: 0;
          	}
          	body {
          		background-color: #CC0033;
          		background-size: cover;
          		background-position: center center;
          		height: 100%;
          	}
          	.container {
          		margin: 0;
          		color: #fff;
          		line-height: normal;
          		position: absolute;
          		align-items: center;
          		left: 5%;
          		right: 5%;
          	}
          	.container h2 {
          		font-size: 2em;
          		text-align: center;
          		margin: 10% 0;
          		color: #fff;
          	}
          	.container h2 span {
          		color: #fff;
          		display: block;
          		text-align: center;
          		font-size: 2.3em;
          		font-weight: 800;
          		letter-spacing: 2px;
          	}
          	.countdown {
          		display: flex;
          		justify-content: space-around;
          		margin: 0;
          	}
          	.countdown div {
          		width: 20%;
          		height: 13vw;
          		margin: 0 10px;
          		line-height: 13vw;
          		font-size: 2em;
          		position: relative;
          		text-align: center;
          		background: #444444;
          		color: #ffffff;
          		font-weight: 500;
          		border-radius: 10px 10px 0 0;
          	}
          	.countdown div:before {
          		content: '';
          		position: absolute;
          		bottom: -30px;
          		left: 0;
          		width: 100%;
          		height: 30px;
          		background: #fff;
          		color: #CC0000;
          		font-size: 0.4em;
          		line-height: 30px;
          		font-weight: 400;
          		border-radius: 0 0 10px 10px;
          	}
          	.countdown #day:before {
          		content: '天';
          	}
          	.countdown #hour:before {
          		content: '時(shí)';
          	}
          	.countdown #minute:before {
          		content: '分';
          	}
          	.countdown #second:before {
          		content: '秒';
          	}
          }

          這是css樣式的代碼:手機(jī)端

          * {
          	margin: 0;
          	padding: 0;
          	font-family: 'Poppins', sans-serif;
          }
          @media screen and (min-width: 1025px) {
          	body {
          		background-color: #CC0033;
          		background-attachment: fixed;
          		background-size: cover;
          		-webkit-background-size: cover;
          		-o-background-size: cover;
          	}
          	.container {
          		position: absolute;
          		top: 80px;
          		left: 100px;
          		right: 100px;
          		bottom: 80px;
          		background-size: cover;
          		-webkit-background-size: cover;
          		-o-background-size: cover;
          		display: flex;
          		justify-content: center;
          		align-items: center;
          		flex-direction: column;
          		box-shadow: 0 50px 50px rgba(0, 0, 0, 0.8),
          			0 0 0 100px rgba(0, 0, 0, 0.3);
          	}
          	.container h2 {
          		text-align: center;
          		font-size: 4em;
          		line-height: 1.5em;
          		color: #ffffff;
          		margin-top: -80px;
          	}
          	.container h2 span {
          		display: block;
          		font-weight: 400;
          		letter-spacing: 6px;
          		font-size: 1em;
          	}
          	.countdown {
          		display: flex;
          		margin-top: 50px;
          	}
          	.countdown div {
          		position: relative;
          		width: 100px;
          		height: 100px;
          		line-height: 100px;
          		text-align: center;
          		background: #333;
          		color: #fff;
          		margin: 0 15px;
          		font-size: 3em;
          		font-weight: 500;
          		border-radius: 10px 10px 0 0;
          	}
          	.countdown div:before {
          		content: '';
          		position: absolute;
          		bottom: -30px;
          		left: 0;
          		width: 100%;
          		height: 35px;
          		background: #b00000;
          		color: #ffffff;
          		font-size: 0.35em;
          		line-height: 35px;
          		font-weight: 300;
          		border-radius: 0 0 10px 10px;
          	}
          	.countdown #day:before {
          		content: '天';
          	}
          	.countdown #hour:before {
          		content: '時(shí)';
          	}
          	.countdown #minute:before {
          		content: '分';
          	}
          	.countdown #second:before {
          		content: '秒';
          	}
          }
          canvas {
          	width: 100%;
          	height: 100%;
          }
          ::-webkit-scrollbar {
          	display: none;
          }
          #btn{
            margin: 40px;
            width: 100px;
            height: 30px;
            background: pink;
            text-align: center;
            color: darkred;
            line-height: 30px;
          }

          最后是邏輯JS代碼

          class Snowflake {
            constructor() {
              this.x = 0;
              this.y = 0;
              this.vx = 0;
              this.vy = 0;
              this.radius = 0;
              this.alpha = 0;
              this.reset();
            }
            reset() {
              this.x = this.randBetween(0, window.innerWidth);
              this.y = this.randBetween(0, -window.innerHeight);
              this.vx = this.randBetween(-3, 3);
              this.vy = this.randBetween(2, 5);
              this.radius = this.randBetween(1, 4);
              this.alpha = this.randBetween(0.1, 0.9);
            }
            randBetween(min, max) {
              return min + Math.random() * (max - min);
            }
            update() {
              this.x += this.vx;
              this.y += this.vy;
              if (this.y + this.radius > window.innerHeight) {
                this.reset();
              }
            }
          }
          class Snow {
            constructor() {
              this.canvas = document.createElement('canvas');
              this.ctx = this.canvas.getContext('2d');
              document.body.appendChild(this.canvas);
              window.addEventListener('resize', () => this.onResize());
              this.onResize();
              this.updateBound = this.update.bind(this);
              requestAnimationFrame(this.updateBound);
              this.createSnowflakes();
            }
            onResize() {
              this.width = window.innerWidth;
              this.height = window.innerHeight;
              this.canvas.width = this.width;
              this.canvas.height = this.height;
            }
            createSnowflakes() {
              const flakes = window.innerWidth / 4;
              this.snowflakes = [];
              for (let s = 0; s < flakes; s++) {
                this.snowflakes.push(new Snowflake());
              }
            }
            update() {
              this.ctx.clearRect(0, 0, this.width, this.height);
              for (let flake of this.snowflakes) {
                flake.update();
                this.ctx.save();
                this.ctx.fillStyle = '#FFF';
                this.ctx.beginPath();
                this.ctx.arc(flake.x, flake.y, flake.radius, 0, Math.PI * 2);
                this.ctx.closePath();
                this.ctx.globalAlpha = flake.alpha;
                this.ctx.fill();
                this.ctx.restore();
              }
              requestAnimationFrame(this.updateBound);
            }
          }
          new Snow();
          var stop = false;
          function show_runtime() {
            var newDay = '2023/1/21 00:00:00';
            var countDate = new Date(newDay);
            var now = new Date().getTime();
            gap = countDate - now;
            var second = 1000;
            var minute = second * 60;
            var hour = minute * 60;
            var day = hour * 24;
            var d = Math.floor(gap / day);
            var h = Math.floor((gap % day) / hour);
            var m = Math.floor((gap % hour) / minute);
            var s = Math.floor((gap % minute) / second);
            if ((d, h, m, s < 0)) {
              stop = true;
            } else {
              document.getElementById('day').innerText = d;
              document.getElementById('hour').innerText = h;
              document.getElementById('minute').innerText = m;
              document.getElementById('second').innerText = s;
            }
          }
          function newyear() {
            document.getElementById('title').innerText = 'Happy Spring Festival';
            document.getElementById('day').innerText = '春';
            document.getElementById('hour').innerText = '節(jié)';
            document.getElementById('minute').innerText = '快';
            document.getElementById('second').innerText = '樂';
          }
          var time = setInterval(() => {
            show_runtime();
            if (stop === true) {
              newyear();
              clearInterval(time);
            }
          }, 1000);
          window.onload = downTime;

          將代碼寫好后在瀏覽器上運(yùn)行一下效果

          這是從代碼編輯器上運(yùn)行瀏覽器

          在PC瀏覽器上運(yùn)行 倒計(jì)時(shí)代碼 效果

          手機(jī)瀏覽器上運(yùn)行倒計(jì)時(shí)代碼的效果


          好了,一個(gè)簡單的小案列就展示到這,喜歡的同學(xué)們可以動(dòng)手試一下,不懂的同學(xué)可以在我的視頻課程中去學(xué)習(xí),希望能夠幫到您們,新的一年相遇就是緣分,很高興認(rèn)識(shí)你們,祝愿您們新的一年里順順利利,好事連連!!!


          主站蜘蛛池模板: 国产成人精品视频一区二区不卡 | 精品国产一区二区三区久久影院| 国产精品一区三区| 国产福利一区二区三区视频在线| 亚洲AV无码一区二区三区牛牛| 久久精品国产第一区二区三区| 一区二区三区91| 麻豆国产在线不卡一区二区| 激情内射亚洲一区二区三区爱妻| 另类免费视频一区二区在线观看| 国产成人精品一区在线| 高清一区高清二区视频| 一区视频免费观看| 一区二区在线免费视频| 一区二区三区波多野结衣| 亚洲AV无码一区二区三区在线观看| 国产一区二区免费在线| 日韩AV片无码一区二区不卡| 成人区人妻精品一区二区不卡| 国产日韩一区二区三区| 中文字幕乱码人妻一区二区三区| 日韩精品一区二区三区中文| 亚洲av日韩综合一区在线观看| 久久精品国产一区二区三区肥胖| 日本免费一区尤物| 色一情一乱一伦一区二区三区 | 一区二区不卡视频在线观看| 国产麻豆剧果冻传媒一区| 国产一区二区三区露脸| 丰满岳乱妇一区二区三区| AV怡红院一区二区三区| 日本高清不卡一区| 精品欧洲av无码一区二区| 国模无码一区二区三区| 国产精品免费综合一区视频| 亚洲国产一区二区视频网站| 亚洲韩国精品无码一区二区三区| 亚洲一区二区三区四区在线观看| 亚洲一区二区三区国产精华液| 日韩欧美一区二区三区免费观看| 国产人妖在线观看一区二区|