整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          “用 40 億條 if 語句,只為判斷一個數字是奇是偶?”

          譯 | 鄭麗媛
          出品 | CSDN(ID:CSDNnews)

          看到這個標題,相信大多數人的第一反應是:真的有人用 40 億條 if 語句,只為判斷一個數字是奇數還是偶數?的確有,這個開發者名為 Andreas Karlsson,他還把整個過程都整理成文了。

          或許由于這“40 億條 if 語句”聽起來實在震撼,Andreas Karlsson 分享的這篇文章在 Hacker News 上很快引起了極大的關注和討論,而他在文中也直白表示:其實這個想法,最初源于一個充滿惡評的短視頻。

          下為譯文:

          大于 11 的數字,沒有輸出結果

          我最近在火車上刷手機時,偶然發現了上面這個截圖:“寫了一個程序,來判斷一個數字是偶數還是奇數。”點開評論區,果然是一連串的惡意評論,多數都在嘲笑這位新手程序員的稚嫩和無知,竟企圖以這種方式解決計算機科學中的經典問題“取模運算”。

          可看過截圖中的代碼和網友評論后,我莫名生出了一些不同的想法:現在,AI 正在分分鐘取代程序員、搶走他們的飯碗,并徹底改變了我們對代碼的思考方式,或許我們應該更加開放地接受這個行業新生代的思想?

          其實仔細想來,上述代碼是時間和空間的一種完美權衡:你在付出自己時間的同時,也換來了計算機的內存和時間——這難道不是一個神奇的算法嗎?

          于是,我開始探索這種只使用比較來判斷一個數字是奇數還是偶數的想法,看看它在實際情況中的效果到底如何。由于我是一位高性能代碼的忠實擁護者,因此我決定用 C 語言來實現這個想法。

          然后,我就開始編碼了:

          • /* Copyright 2023. All unauthorized distribution of this source code  will be persecuted to the fullest extent of the law*/#include <stdio.h>#include <stdint.h>#include <stdlib.h>int main(int argc, char* argv[]){ uint8_t number = atoi(argv[1]); // No problems here if (number == 0) printf("even\n"); if (number == 1) printf("odd\n"); if (number == 2) printf("even\n"); if (number == 3) printf("odd\n"); if (number == 4) printf("even\n"); if (number == 5) printf("odd\n"); if (number == 6) printf("even\n"); if (number == 7) printf("odd\n"); if (number == 8) printf("even\n"); if (number == 9) printf("odd\n"); if (number == 10) printf("even\n");}

            接下來,我們要編譯這段代碼,使用 /Od 禁用優化,確保煩人的編譯器不會干擾我們的算法。編譯完成后,我們就可以對程序進行快速測試,看看結果如何:

            • PS > cl.exe /Od program.cPS > .\program.exe 0 evenPS > .\program.exe 4evenPS > .\program.exe 3oddPS > .\program.exe 7odd

              結果顯示:0、4 是偶數,3、7 是奇數。這么看來,程序似乎運行得挺好,但在進一步測試后,我發現了一些問題:

              • PS > .\program.exe 50PS > .\program.exe 11PS > .\program.exe 99

                大于 11 的數字沒有輸出,看來這個程序只對 11 以下的數字有效!回到原始代碼中,可以發現問題出在最后一個 if 語句之后:我們需要更多的 if 語句!

                向 32 位(32-bit)數擴展

                這件事進行到這里,就需要我在時間和內存之間做出權衡了。考慮到我的壽命有限,我決定用另一種編程語言對 if 語句進行元編程。為了彌補這種“作弊”行為,我決定用“地球上速度最慢”的語言 Python。

                • print("/* Copyright 2023. All unauthorized distribution of this source code")print(" will be persecuted to the fullest extent of the law*/")

                  print("#include <stdio.h>")print("#include <stdint.h>")print("#include <stdlib.h>")

                  print("int main(int argc, char* argv[])")print("{")print(" uint8_t number = atoi(argv[1]); // No problems here")

                  for i in range(2**8): print(" if (number == "+str(i)+")") if i % 2 == 0: print(" printf(\"even\n\");") else: print(" printf(\"odd\n\");")

                  print("}")

                  好了!現在我們可以生成一個程序,解決所有 8 位(8-bit)整數的奇偶問題!

                  • PS > python programmer.py > program.cPS > cl.exe /Od program.cPS > .\program.exe 99oddPS > .\program.exe 50evenPS > .\program.exe 240evenPS > .\program.exe 241odd

                    看看,這個效果簡直完美!現在,讓我們把它放大到 16 位(16-bit)!

                    • print(" uint16_t number = atoi(argv[1]); // No problems here")for i in range(2**16):

                      這樣就得到了一個約 13 萬行、超長且漂亮的 c 文件。回顧了一下我多年工作所做的一些代碼庫,這其實不算什么。話不多說,開始編譯!

                      • PS > python programmer.py > program.cPS > cl.exe /Od program.cPS > .\program.exe 21000evenPS > .\program.exe 3475 oddPS > .\program.exe 3 oddPS > .\program.exe 65001oddPS > .\program.exe 65532even

                        太棒了,我們的算法似乎能夠處理大量數據!可執行文件大約只有 2 MB,但這與我擁有高達 31.8 GB 內存的強大游戲設備相比,簡直不值一提。

                        但眾所周知,32 位(32-bit)才是計算機領域的終極目標,也是我們解決所有實際工程和科學問題所需的最終位寬。畢竟,在 IPv4 因所謂的 "地址耗盡 "而被認為過時 60 年后,它如今仍然很強大。所以,讓我們來看看最終的規模:32 位的數字是 16 位的 65536 倍,這會有什么問題嗎?

                        • print(" uint32_t number = atoi(argv[1]); // No problems here")for i in range(2**32):

                          于是,我讓強大的 Python 開始它的工作。48 小時后,我喝了一杯咖啡,然后回來檢查程序,就得到了一個美麗的 c 文件,大小接近 330 GB!我幾乎可以肯定,這是歷史上最大的 c 文件之一。當我輸入下一條命令時,我的手指都在顫抖,我猜 MSVC 肯定從未遇到如此強大的源代碼。

                          在我那臺可憐而強大的電腦頁面文件中遭受半小時的折磨后,輸出如下:

                          • PS > cl /Od program.cMicrosoft (R) C/C++ Optimizing Compiler Version 19.32.31329 for x64Copyright (C) Microsoft Corporation. All rights reserved.

                            program.cprogram.c(134397076): warning C4049: compiler limit: terminating line number emissionprogram.c(134397076): note: Compiler limit for line number is 16777215program.c(41133672): fatal error C1060: compiler is out of heap space

                            太令人失望了!不僅編譯器讓我失望,在研究 Windows 可移植可執行文件格式(.exe)的限制時,我發現它無法處理超過 4GB 的文件!由于需要將 40 多億次比較語句編碼到可執行文件中,這對于實現我們的算法是一個主要障礙。即使每次比較時使用的字節數少于一個,對我來說工作量也太大了。

                            不過,糟糕的編譯器和文件格式不應該阻止我們實現夢想。畢竟,編譯器所做的只是將一些花哨的機器代碼寫入文件,而文件格式只是一些結構,告訴操作系統如何將二進制代碼放入內存——其實,我們自己就能做到。

                            解決最后一個問題,程序性能很不錯

                            讓我們先用 x86-64 匯編語言編寫一個 IsEven 函數,因為這是我 Intel 處理器驅動的本地語言,它看起來是這樣的:

                            • ; Argument is stored in ECX, return value in EAXXOR EAX, EAX ; Set eax to zero (return value for odd number)CMP ECX, 0h ; Compare arg to 0 JNE 3h ; Skip next two instructions if it wasn't equalINC EAX ; It was even, set even return value (1)RET ; ReturnCMP ECX, 1h ; Compare arg to 1JNE 2 ; Skip next instruction if not equalRET ; Odd return value already in EAX, just RET; add the next 2...2^32-1 comparisons hereRET ; Fallback return

                              這并不是真正正確的匯編代碼,但這不重要,因為我們要手動將其編譯成機器代碼。

                              你問我是怎么做到的?我上網查閱了x86(-64) 體系結構手冊,還利用我早年編寫仿真器和黑客經驗,找出了每條指令的正確操作碼和格式……開個玩笑,這是不可能的。實際上,我是直接問 ChatGPT 每條指令的正確操作碼是什么,幸運的是,它也沒有產生 x86-64 的任何新擴展。

                              所以現在我們只需編寫一個“編譯器”來輸出這段代碼。請注意,我們將直接使用從 AI 獲取的指令操作碼,下面是用 Python 編寫的代碼:

                              • import struct

                                with open('isEven.bin', 'wb') as file:
                                file.write(b"\x31\xC0") # XOR EAX, EAX

                                for i in range(2**32): ib = struct.pack("<I", i) # Encode i as 32 bit little endian integer

                                file.write(b"\x81\xF9" + ib) # CMP ECX, i

                                if i%2 == 0: file.write(b"\x75\x03") # JNE +3 file.write(b"\xFF\xC0") # INC EAX file.write(b"\xC3") # RET else: file.write(b"\x75\x01") # JNE +1 file.write(b"\xC3") # RET

                                file.write(b"\xC3") # Fallback RET

                                雖然我們在一定程度上偏離了開頭 TikTok 帖子的最初構想,但本質并沒有改變:我們創建了一個非常長的 if 語句列表,用于確定某個數字是奇數還是偶數,并忽略了任何有助于簡化問題的算術運算。

                                運行這個程序后,我們就得到了一個 40GB 的文件,其中包含了確定 32 位數字是偶數還是奇數所需的全部 42 億次比較!現在,我們只需編寫能夠加載和使用這些指令的主程序。為了提高性能(這一點非常重要),我決定將文件映射到地址空間,而非一次性讀取全部文件。這樣,我們就可以假裝整個文件已經在內存中,讓可憐的操作系統來處理將一個 40GB 的 Blob 裝入虛擬內存的問題。用 READ 和 EXECUTE 權限映射文件后,我們就可以使用函數指針調用代碼了,代碼如下:

                                • #include <stdio.h>#include <Windows.h>#include <stdint.h>

                                  int main(int argc, char* argv[]){ uint32_t number = atoi(argv[1]); // No problems here

                                  // Open code file HANDLE binFile = CreateFileA( "isEven.bin", GENERIC_READ | GENERIC_EXECUTE, FILE_SHARE_READ, , OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, );
                                  // Get 64 bit size of file LARGE_INTEGER codeSize; GetFileSizeEx(binFile, &codeSize);

                                  // Create memory map of the file HANDLE mapping = CreateFileMapping( binFile, , PAGE_EXECUTE_READ, 0, 0, );

                                  // Get a pointer to the code LPVOID code = MapViewOfFile( mapping,FILE_MAP_EXECUTE | FILE_MAP_READ, 0, 0, codeSize.QuadPart);

                                  // Create a function that points to the code int (*isEven)(int) = (int(*)(int))code;

                                  if (isEven(number)) printf("even\n"); else printf("odd\n");

                                  CloseHandle(binFile);}

                                  就是這樣!現在我們已經具備了判斷任何 32 位(32-bit)數字是奇是偶的所有功能,讓我們試一試:

                                  • PS >.\program.exe 300evenPS >.\program.exe 0evenPS >.\program.exe 1000000evenPS >.\program.exe 100000007oddPS >.\program.exe 400000000evenPS >.\program.exe 400000001oddPS >.\program.exe 400000006evenPS >.\program.exe 4200000000odd <---- WRONG!

                                    差不多了!似乎算法在符號性方面有問題,任何超過 2^31 的值似乎都會給出隨機結果。那么,讓我們來修復最后一個錯誤:原來 atoi 不能處理無符號性,所以它無法解析大數字。用 strtoul 代替它就能解決所有問題。

                                    uint32_t number = strtoul(argv[1], , 10);// No problems here
                                    • PS >.\program.exe 4200000000evenPS >.\program.exe 4200000001odd

                                      順便提一句,這個程序的性能很不錯。對于小數字,結果能即時返回,而對于接近 2^32 極限的大數字,結果仍能在大約 10 秒內返回。考慮到計算機必須從磁盤讀取 40GB 的數據,將其映射到物理內存,然后讓 CPU 在沒有緩存的情況下對其進行處理,老實說這個速度已經相當令人驚嘆了。作為參考,我電腦的配置為 Core i5 12600K,32GB 內存,文件存儲在 M.2 SSD 硬盤上。在計算過程中,我看到 SSD 硬盤的峰值讀取速度約為 800 MB/s。

                                      至此,互聯網再次被證明是錯誤的:你不僅可以按照 TikTok 帖子的方式編寫一個功能齊全、性能良好的程序,而且還非常有趣。

                                      網友:沒必要,一個簡單的 for 循環就能解決

                                      不過 Andreas Karlsson 的分享,并沒有得到部分開發者的認可,甚至認為他有些“嘩眾取寵”:

                                      • “在我看來,這幾乎是過度設計。為什么要費盡心思生成代碼?只需一個簡單的‘for 循環’就能解決。”

                                      • func isOdd(n int) bool { var odd bool for i := 0; i < n; i++ { odd = !odd } return odd }
                                        • 真正高質量的運行應始終使用遞歸。

                                        • func isOdd(n int) bool { switch { case n == 0: return false case n > 0: return !isOdd(n-1) default: return !isOdd(n+1) } }

                                          還有人指出:“我完全聽不懂這個笑話。我們從中學到了什么?exe 文件不能超過4GB?一個2^32 if 的程序大約是300GB?這看起來并不瘋狂,只是毫無意義。”

                                          對此,有人反駁道:“可笑的是,他真的做到了。幾十年來,人們一直在拿它開玩笑,但這個人是認真的。由于代碼多得太極端,沒有編譯器可以處理,甚至沒有任何已知的匯編程序,因此他必須生成自己的機器碼二進制文件才能運行。結果最后,他居然還真的成功了。”

                                          參考鏈接:

                                          https://andreasjhkarlsson.github.io/jekyll/update/2023/12/27/4-billion-if-statements.html

                                          https://news.ycombinator.com/item?id=38790597

          的一個朋友在最近的一次面試中遇到了一個有趣的 CSS 面試問題。當我第一次看到這個問題時,我認為這是一個常見的CSS問題。然而,經過仔細研究,我發現了這個問題的有趣部分。

          面試題:請用CSS實現如下效果:




          頁面上有一些數字顯示文章閱讀的數量。如果數字小于 100,則數字的顏色為灰色。如果數字大于或等于 100,則數字為棕色,而且這種顏色可以動態調整,而不是提前預設。


          最后面試官要求用純CSS來解決這個問題,你知道如何達到這個效果嗎?

          題目分析

          這個問題的本質是什么?

          這個問題的本質很簡單,也就是說,這實際上是一個 if-else 問題。

          如果我們用偽代碼描述這個問題,它應該是這樣的:

          let color;
          if (reads < 100){
            color = 'gray'
          } else {
            color = 'brown'
          }
          

          所以現在問題變成了:我們如何在 CSS 中實現這個 if-else 邏輯?請記住,CSS 中沒有 if-else 關鍵字之類的東西。

          CSS 中實現 if-else

          在 CSS 中實現 if-else 的邏輯是本題考查的核心技能。讓我們在下面完成這個邏輯。如果你學會了這個技巧,你可以用它來實現許多強大的 CSS 效果。

          首先,讓我們了解一個叫做clamp的函數。

          clamp() CSS 函數將一個值限制在上限和下限之間。clamp() 允許在定義的最小值和最大值之間的值范圍內選擇中間值。

          基本語法格式:

          clamp(min, var, max)
          

          我們可以將clamp函數理解為這樣的偽代碼:

          funciton clamp(min, var, max){
            if(var <= min){
              return min
            }
          
            if(var >= max){
              return max
            }
          
            if(var > min && var < max){
              return var
            }
          }
          

          所以:

          clamp(10, 13, 20) → 13

          clamp(10, 2, 20) → 10

          clamp(10, 30, 20) → 20

          用法示例:


          font-size 的值不會超過 20px,也不會低于 10px。

          這是clamp的基本用法。

          如果您對clamp仍有疑問,可以參考 MDN 文檔。

          接下來,我們在 CSS 中實現這個功能。

          result的值根據 var 的值而變化:

          當 var 的值小于 100 時,結果的值為 10;

          當 var 的值大于等于 100 時,結果變為 20。

          如果我們用偽代碼描述這個問題,它應該是這樣的:

          let result;
          if(var < 100){
            result = 10
          } else {
            result = 20
          }
          

          這個要求和clamp函數類似,但又不一樣。clamp可以將 var 的值限制在一個范圍內,但我們現在希望結果的值是 10 或 20。

          那我們怎么做?

          有一個特殊的技巧:我們可以放大 var 的變化,使其值要么達到區間的上限,要么達到區間的下限。

          于是:

          let result = clamp(10, (var-99) * 20, 20)
          

          這會產生一個效果:

          • 如果 var 的值為 99,則表達式變為:clamp(10, 0, 20), takes 10.
          • 如果 var 的值為 100,則變為:clamp(10, 20, 20), takes 20.

          用一張圖解釋:




          同樣,如果我們希望:

          當 var 的值小于 50 時,result的值為 5。

          當 var 的值大于等于 50 時,result的值為 15。

          我們只需要這樣寫:

          let result = clamp(5, (var-49) * 15, 15)
          

          你有沒有注意到:這實際上是 if-else 的效果,我們做到了。




          CSS 中切換顏色

          回到最初的面試問題。

          為了讓我們后面可以使用 CSS 進行變量計算,我們需要將值放在一個 CSS 變量中,所以 HTML 可以這樣寫:

          <num style=<span data-raw-text="" "="" data-textnode-index-1655261252004="96" data-index-1655261252004="1664" class="character" style="margin: 0px; padding: 0px;">"--num:1<span data-raw-text="" "="" data-textnode-index-1655261252004="96" data-index-1655261252004="1672" class="character" style="margin: 0px; padding: 0px;">">1<span>reads</span></num>
          <num style=<span data-raw-text="" "="" data-textnode-index-1655261252004="99" data-index-1655261252004="1710" class="character" style="margin: 0px; padding: 0px;">"--num:99<span data-raw-text="" "="" data-textnode-index-1655261252004="99" data-index-1655261252004="1719" class="character" style="margin: 0px; padding: 0px;">">99<span>reads</span></num>
          <num style=<span data-raw-text="" "="" data-textnode-index-1655261252004="102" data-index-1655261252004="1758" class="character" style="margin: 0px; padding: 0px;">"--num:102<span data-raw-text="" "="" data-textnode-index-1655261252004="102" data-index-1655261252004="1768" class="character" style="margin: 0px; padding: 0px;">">102<span>reads</span></num>
          

          如果我們不需要考慮 HTML 語義或 SEO 因素,這里的“數字”和“讀取”都可以由偽元素生成:

          <head>
            <style>
              num::before {
                counter-reset: num var(--num);
                content: counter(num);
              }
          
              num::after {
                content: 'reads';
              }
          </style>
          </head>
          
          <body>
            <div>
              <num style=<span data-raw-text="" "="" data-textnode-index-1655261252004="121" data-index-1655261252004="2033" class="character" style="margin: 0px; padding: 0px;">"--num:1<span data-raw-text="" "="" data-textnode-index-1655261252004="121" data-index-1655261252004="2041" class="character" style="margin: 0px; padding: 0px;">"></num>
              <num style=<span data-raw-text="" "="" data-textnode-index-1655261252004="124" data-index-1655261252004="2064" class="character" style="margin: 0px; padding: 0px;">"--num:99<span data-raw-text="" "="" data-textnode-index-1655261252004="124" data-index-1655261252004="2073" class="character" style="margin: 0px; padding: 0px;">"></num>
              <num style=<span data-raw-text="" "="" data-textnode-index-1655261252004="127" data-index-1655261252004="2096" class="character" style="margin: 0px; padding: 0px;">"--num:102<span data-raw-text="" "="" data-textnode-index-1655261252004="127" data-index-1655261252004="2106" class="character" style="margin: 0px; padding: 0px;">"></num>
            </div>
          </body>
          

          如果對 content 和 counter-reset 不熟悉,可以查看 MDN 文檔。

          • content :https://developer.mozilla.org/en-US/docs/Web/CSS/content
          • counter-reset:https://developer.mozilla.org/en-US/docs/Web/CSS/counter-reset

          具體的演示效果,可以通過以下地址查看:https://codepen.io/bytefishmedium/pen/VwQrGEb

          棕色為#aa540e,用HSL顏色表示為hsl(27, 50%, 36%),如下:




          它的飽和度控制顏色的鮮艷程度。飽和度越高,顏色越鮮艷,飽和度越低,顏色越暗。當飽和度降低到0時,就變成了完全的灰色,如下:




          在灰色和棕色之間切換顏色,即在 hsl(27, 85%, 36%) 和 hsl(27, 85%, 36%) 之間切換。

          于是就有如下代碼:

          num{
            --s: clamp(0%,(var(--num) - 99) * 99%,85%);/* >100 */
            color: hsl(27 var(--s) 36%);
          }
          

          最終的演示:https://codepen.io/bytefishmedium/pen/WNMXabm

          總結

          我們通過clamp函數在CSS中實現if-else效果,最后讓顏色根據變量的值進行切換。

          其實原面試題還有另外一部分,簡單來說就是:讓顏色在多個值之間切換。僅使用 if-else 不足以滿足此要求,有興趣的話,可以留言交流學習。

          -

          近在群里,有個小伙伴問了這么一道很有趣的問題:

          1. CSS 能否實現,容器再某個高度下是某種表現,一旦超出某個高度,則額外展示另外一些內容

          為了簡化實際效果,我們看這么一張示意效果圖:

          可以看到,當容器高度沒有超過某一個值時,沒有箭頭圖標。反之,箭頭圖標出現。

          這個效果在很多場景都會出現,可以算是一個高頻場景,那么在今天,我們能否不使用 JavaScript,僅僅憑借 CSS 實現類似于這樣的功能呢?

          答案當然是可以,XBoxYan 大佬在 CSS 實現超過固定高度后出現展開折疊按鈕 介紹了一種非常巧妙的借助浮動的解法,十分有意思,感興趣的同學可以先行一步了解。

          當然,浮動 float 在現如今的 CSS 世界,運用的已經非常少了。那么除了浮動,還有沒有其它有意思的解法?本文我們將一起來探究探究。

          方法一:借助最新的容器查詢

          第一種方法,非常簡單,但是對兼容性有所要求。那就是使用容器查詢 -- @container 語法。

          容器查詢在 新時代布局新特性 -- 容器查詢 也詳細介紹過。

          簡單而言,容器查詢它給予了 CSS,在不改變瀏覽器視口寬度的前提下,只是根據容器的寬度或者高度變化,對布局做調整的能力。

          基于這個場景,我們假設我們有如下的 HTML/CSS 結構:

          <div class="g-container">
              <div class="g-content">
                  Lorem ipsum dolor s...
              </div>
          </div>
          
          .g-container {
              position: relative;
              width: 300px;
              height: 300px;
              resize: vertical;
              overflow: hidden;
          
              .g-content {
                  height: 100%;
              }
          
              .g-content::before {
                  content: "↑";
                  position: absolute;
                  bottom: 0px;
                  left: 50%;
                  transform: translate(-50%, 0);
              }
          }
          

          它是這么一個樣式效果:

          其中,我們給元素 .g-content 添加了 resize: vertical,讓它變成了一個可以在豎直方向上通過拖動改變高度的容器,以模擬容器在不同內容的場景下,高度不一致的問題:

          我們通過元素的偽元素實現了箭頭 ICON,并且它是一直顯示在容器內的。

          下面,我們通過簡單的幾句容器查詢代碼,就可以實現讓箭頭 ICON,只在容器高度超過特定高度的時候才出現:

          .g-container {
              container-type: size;
              container-name: container;
          }
          
          @container container (height <= 260px) {
              .g-content::before {
                  opacity: 0;
              }
          }
          

          簡單解釋一下:

          1. .g-container 它被用作容器查詢的目標容器container-type 屬性指定了容器的類型為 size,表示我們將使用容器的尺寸來應用樣式。container-name 屬性指定了容器的名稱為 container,以便在后面的容器查詢規則中引用。
          2. @container container (height <= 260px) {} 表示這是一個容器查詢規則,在括號中的條件 (height <= 260px) 表示當容器的高度小于等于 260px 時,應用該規則下的樣式
          3. 具體規則為,如果容器的高度小于等于 260px 時,.g-content 元素的偽元素將變得透明

          這樣,我們就非常簡單的實現了容器在不同高度下,ICON 元素的顯示隱藏切換:

          完整的代碼,你可以戳這里:CodePen Demo -- flexible content

          當然,這個方案的唯一缺點在于,截止至今天(2023-11-11),兼容性不是那么好:

          那,有沒有兼容性更好的方案?當然,來我們一起來看看 clamp + calc 的方案。

          方法二:clamp+calc大顯神威

          上面效果的核心在于:

          1. 如果容器的高度大于某個值,顯示樣式 A
          2. 如果容器的高度小于等于某個值,顯示樣式 B

          那么想想看,如果拿容器的高度減去一個固定的高度值,會發生什么?假設一下,ICON 元素的 CSS 代碼如下:

          .g-content::before {
              content: "↑";
              position: absolute;
              left: 50%;
              transform: translate(-50%, 0);
              bottom: calc(100% - 200px);
          }
          

          仔細觀察 bottom: calc(100% - 200px),在元素的 bottom 屬性中,100% 表示的是容器當前的高度,因此 calc(100% - 200px) 的含義就代表,容器當前高度減去一個固定高度 200px。因此:

          1. 當容器高度大于 200pxcalc(100% - 200px) 表示的是一個正值
          2. 當容器高度小于 200pxcalc(100% - 200px) 表示的是一個負值
          3. 當容器高度等于 200pxcalc(100% - 200px) 表示 0

          我們看看這種情況下,整個 ICON 的表現是如何的:

          可以看到,當容器高度大于 200px 的時候,箭頭 ICON 確實出現了,但是,它無法一直定位在整個容器的最下方

          有什么辦法讓它在出現后,一直定位在容器的最下方嗎?

          別忘了,CSS 中,還有幾個非常有意思的數學函數:min()max()clamp(),它們可以有效限定動態值在某個范圍之內!

          不太了解的,可以看看這篇 現代 CSS 解決方案:CSS 數學函數

          利用 clamp(),我們可以限定計算值的最大最小范圍,在這個場景下,我們可以限制 bottom 的最大值為 10px

          .g-content::before {
              // ...
              bottom: clamp(-99999px, calc(100% - 200px), 10px);
          }
          

          上面的代碼 clamp(-99999px, calc(100% - 200px), 10px),核心在于,如果 calc(100% - 200px) 的計算值大于 10px,它只會取值為 10px,利用這個技巧,我們可以在容器高度超長時,把箭頭 ICON 牢牢釘在容器的下方,無論容器的高度是多少:

          到此,結束了嗎?顯然沒有。

          雖然上面的代碼,解決當 calc(100% - 200px) 的計算值大于 10px 的場景,但是沒有解決,當 calc(100% - 200px) 的計算值處于 -10px ~ 10px 這個范圍內的問題。

          我們可以清楚的看到,當我們往下拖動容器變高的時候,箭頭元素是逐漸慢慢向上出現,而不是突然在某一個高度下,直接出現,所以在實際使用中,會出現這種 ICON 只出現了一半的尷尬場景:

          但是,莫慌!這個問題也好解決,我們只需要給 calc(100% - 200px) 的計算值,乘上一個超級大的倍數即可。原因在于:

          1. calc(100% - 200px) 的計算值是負數時,我們其實不希望 ICON 出現,此時,乘上一個超級大的倍數,依然是負數,不影響效果
          2. calc(100% - 200px) 的計算值是正數時,為了避免 ICON 處在只漏出部分的尷尬場景,通過乘上一個超級大的倍數,讓整個計算值變得非常大,但是由于又有 clamp() 最大值的限制,無論計算值多大,都只會取 10px

          看看代碼,此時,整個 bottom 的取值就改造成了:

          .g-content::before {
              // ...
              bottom: clamp(-9999px, calc(calc(100% - 200px) * 100000), 10px);
          }
          

          通過,將 calc(100% - 200px) 的值,乘上一個超大的倍數 100000,無論是正值還是負值,我們把計算值放大了 100000 倍。這樣,整個效果就達成了我們想要的效果:

          仔細看上圖,ICON 元素從漸現,變成了瞬間出現!與上面的 @container 效果幾乎一致,最終達成了我們想要的效果。

          其核心就在于 clamp(-9999px, calc(calc(100% - 200px) * 100000), 10px),一定需要好好理解這一段代碼背后的邏輯。

          基于此,我們就巧妙的利用 clamp() + calc() 方法,近似的實現了類似于 if/else 的邏輯,實在是妙不可言!

          CodePen Demo -- flexible content

          原文鏈接:https://www.cnblogs.com/coco1s/p/17831064.html


          主站蜘蛛池模板: 亚洲丰满熟女一区二区哦| 福利片免费一区二区三区| 狠狠综合久久AV一区二区三区| 精品视频一区在线观看| 精品一区二区三区水蜜桃| 一区二区三区在线观看中文字幕| 99国产精品一区二区| 欧美日本精品一区二区三区| 日本一区二区三区精品国产| 激情内射亚洲一区二区三区 | 国产乱码精品一区二区三区中文| 亚洲美女视频一区二区三区| 午夜一区二区在线观看| 亚洲av无码不卡一区二区三区| 久久精品免费一区二区三区| 日韩精品一区二区三区在线观看l| 国产一区二区三区在线| 一区二区三区福利| 老熟女高潮一区二区三区| 一区二区三区高清视频在线观看| 日本精品视频一区二区| 国产精品第一区揄拍| 国产精品亚洲产品一区二区三区| 亚洲AV无码国产一区二区三区| 国产亚洲自拍一区| 国产AV国片精品一区二区| 精品国产亚洲第一区二区三区| 日韩免费一区二区三区在线播放| 日本一区二区免费看| 黑人大战亚洲人精品一区| 香蕉久久AⅤ一区二区三区| 国产一区二区在线视频播放| 国产成人精品一区二区A片带套| 秋霞无码一区二区| 91精品一区二区| 少妇无码一区二区三区免费| 亚洲国产精品一区| 99精品一区二区免费视频 | 久久亚洲中文字幕精品一区四| 国产一区在线视频观看| 精品日韩一区二区三区视频|