useState to store references to DOM elements
In most cases it would go like this: you use useRef to store a reference to a DOM element, and useState to store an "immutable" value that should trigger a re-render. However, this is more like the outcomes. Duo to the underlying mechanism of these 2 hooks, you can achieve much more advanced use cases if you know what you are doing.
const [button, setButton] = useState<HTMLButtonElement | null>(null);
return <button ref={setButton} />
This actually works due to the fact that React's special "ref" prop can receive a callback (remember the "callback ref" concept anyone?), and a setState is basically a callback to set a value, isn't it?
The thing is, when would this be useful? When should you use useState for reference instead of useRef? The key here is that useState triggers a re-render, and useRef does not. So you would use useState when you want a re-render to happen (or to be specific, all the hooks that happen in a re-render to run) when you got a reference to an element (i.e. after it's mounted to the real tree). This is usually seen when the target element is rendered conditionally or it may be replaced later.
This is not actually uncommon. One of the most popular positioning library, PopperJS, use this approach so they can re-calculate the position correctly after having known the rendered target element https://popper.js.org/react-popper/v2/
Một chút về Semantics HTML
Cuối năm chia sẻ nhẹ nhàng chút thôi, khi viết html, đặc biệt là form input, đừng quên các attributes bổ trợ để tăng UX. Ví dụ với một form input nhận credit card. Thay vì viết
<label>Card Number</label>
<input type="text">
Hãy viết
<label for="frmCCNum">Card Number</label>
<input type="text" id="frmCCNum" autocomplete="cc-number" inputmode="numeric" autocorrect="off" spellcheck="false" aria-label="Card number" placeholder="1234 1234 1234 1234">
- Dùng labels kết hợp với id để tăng touch area, người dùng chỉ cần ấn vào labels hoặc input để focus vào input.
- autocomplete để ... auto complete :shrug: nếu người dùng đã save credit card ở browser.
- inputmode="numeric" để chuyển về bàn phím số trên mobile.
- tắt autocorrect và spellcheck vì ko cần thiết với credit card.
- aria-label để phục vụ người dùng trên screen reader.
- placeholder để hint về format.
Hãy nói về filter property
Context: Case rất đơn giản. Bạn có một image. Làm sao để apply manipulation lên đó? Ứng dụng có thể bao gồm: image editing app, apply prebuilt filter như Instagram, hay đơn giản hơn là blur/lighten on hover chẳng hạn.
Usage: Giá trị của filter
là một space-seperated list các filter function. Ví dụ:
filter: blur(20px) brightness(0.8);
Nhưng các bạn cũng có thể specify function ở nhiều chỗ và dựa vào cơ chế cascading của CSS. Các function có thể dùng bao gồm: blur() brightness() contrast() drop-shadow() grayscale() hue-rotate() invert() opacity() saturate() sepia() url() Mọi người có nhu cầu thì tìm hiểu thêm về từng function. Trong đây mình chỉ lưu ý 2 function là:
- drop-shadow: Tại sao có trường hợp ta muốn apply filter này thay vì dùng box-shadow property? Vì thứ nhất, box-shadow đúng như tên gọi của nó, sẽ tạo ra shadow dựa theo box model. drop-shadow sẽ tạo ra shadow đúng như hình dạng thực tế của object (ví dụ như transparent image). Thứ hai, filter sẽ được một số browser GPU-accelerated.
- url: Function này có arg là url/refernce trỏ đến svg filter. Chỉ đơn giản vậy, nếu bạn đã quen hoặc đã có sẵn svg filter thì hãy xài cái này. Pitfalls:
- Thứ tự các function rất quan trọng và có thể làm thay đổi kết quả nhận được. Specify các function ở một list duy nhất so với specify function ở nhiều chỗ (filter propery) khác nhau cũng sẽ có thể cho ra kết quả khác.
- Apply filter hao tài nguyên nên đừng quá lạm dụng. Note: filter đã có độ phủ sóng rộng, nhưng không dùng được đối với IE
Hãy nói về Feature Policy
Context: Các bạn chắc sẽ quen thuộc hơn với một "sibling" của Feature Policy là Content Security Policy (CSP). Như tên gọi phản ánh. CSP đối phó với những vấn đến liên quan đến user security; Feature Policy cho phép kiểm soát feature, API nào available và ai (origin) nào được phép sử dụng chúng.
Cách dùng: Cũng giống như CSP, Feature Policy thường được enable qua HTTP header.
Feature-Policy: geolocation 'self' foo.com; camera 'none'; microphone 'none'
Ở trên ^, chúng ta đang cho phép sử dụng geolocation ở site này, ở iframe cùng origin, và ở iframe từ foo.com. Đồng thời, ta disable hoàn toàn camera và microphone use.
Ngoài cách trên ra, ta còn có thể set Feature Policy trực tiếp cho embedded iframe thông qua allow
attribute. Cú pháp tương tự như khi ta set HTTP header.
Xem ở đây để biết thông tin đầy đủ.
Ứng dụng: Đến đây có thể các bạn sẽ tự hỏi, control feature nào available cho 3rd party origin thì hợp lí rồi, nhưng tại sao ta lại phải set Feature Policy cho chính site/origin mình đang kiểm soát? Câu trả lời là để đảm bảo một best practice nào đó, và đảm bảo rằng nếu chúng ta đã quyết định một browser feature gì đó là no-go thì không có cá nhân nào có thể vô tình hay cố ý đâm chọt được.
Xét theo mục đích, feature policy chia ra làm 2 nhóm chính:
- Để kiểm soát kĩ đối với những feature quan trọng, có nguy cơ cao (như camera, microphone chẳng hạn). Lí do tồn tại của nhóm này đã quá rõ.
- Để cải thiện trải nghiệm người dùng.
Một ví dụ trong nhóm (2) là policy về oversized-images
. Nếu ta set
Feature-Policy: oversized-images 'none';
Nếu một image có dimensions to hơn nhiều so với containing area thì browser không display nó, mà sẽ display placeholder thay vào đó. Có nhiều policy tương tự khác cũng hay ho không kém, mọi người tham khảo thêm ở đây.
Note: Feature Policy còn khá experimental, một số browser chỉ có partial support. Và chính thức thì nó đã bị renamed lại thành Permission Policy.
Hiểu về perspective
Context: Đây là một property trước đây đối với mình rất "magic". Nếu từng xem người ta làm những thứ hay ho, mô phỏng 3d ở codepen, khả năng cao là các bạn đã gặp property này.
Introduction: Giá trị của perspective
là khoảng cách của người xem đến mặt phẳng z=0
của một object (z ở đây là chiều không gian thứ 3, không có liên quan đến z-index).
Để dễ hình dung, tưởng tượng bạn đang xem catwalk, nhìn thẳng chính diện từ phía cuối khán đài, khi bạn set perspective: 50px
, điều này có thể hiểu tương đương như bạn đang ở cách sân khấu 50cm. Khi người mẫu bước đến cao trào, bạn nhìn thấy họ rất rõ, họ ở sát mặt bạn, trong khi những người ở cánh gà bước ra còn thấp thoáng be bé đằng xa. Sự chênh lệch rất lớn.
Ngược lại, nếu bạn đang ngồi ở hàng ghế cuối (perspective: 10000px
), khả năng cao là bạn sẽ thấy người ở cuối đường catwalk, và người mới bước ra cũng nhỏ ngang nhau.
Lấy ví dụ trên để hiểu rằng, giá trị của perspective
liên quan trực tiếp đến perceived intensity của bạn đối với những thay đổi trong không gian 3 chiều. Khi bạn ở càng gần (giá trị perspective
càng thấp) thì thay đổi càng rõ rệt. By default perspective
có giá trị là none
, điều này có nghĩa là ta sẽ skip hoàn toàn những quy luật về perspective như gần to xa nhỏ. Có thể thấy rõ sự khác biệt ở đây (Click rotateY)
Bonus: Để hiểu thêm về ứng dụng của perspective
, mọi người có thể xem qua infamous demo này về pure CSS parallax scrolling (Click debug để thấy cách các layer laid out ở không gian 3d). Concept rất đơn giản: Set perspective cho scroll container, set translateZ có giá trị khác nhau cho các layer. Layer nào được translate ra xa phía sau thì theo quy luật sẽ có tốc độ di chuyển khi scroll chậm hơn những layer ở gần. Side effect là khi đẩy một layer xa nó cũng sẽ trở nên nhỏ hơn. Dựa vào perspective và giá trị của translateZ, ta tính thông số để scale nó lại đúng kích thước ban đầu -> Parallax effect hoàn thành.
@Cậu Làm Vườn
Đi kèm với perspective
còn có một property là perspective-origin
, các bạn cứ hiểu đơn giản là specify góc nhìn của mình, từ khoảng cách perspective đã set.
Typed Tailw.. à nhầm, Typed Configs!
This may be a little bit personal, but you know what grinds my gears? Having to switched to js/json configuration files while working in the comfort world of TypeScript. I was pretty spoiled and feeling uneasy when having no autocompletion or inlined linting when writing configuration files (and some configs do feel like source code).
Fortunately, there is not just one, but two ways to solve this!
- The JSON-Schema way: JSON Schemas are basically typing for JSON files, and fortunately some editors like VSCode have built-in support for many common JSON config format: https://www.schemastore.org/json/ (in fact, if you are a library author you can even add your own there!).
To have this, simply name your JSON config correctly (e.g. .prettierrc.json). In fact, this should already be done because if not then the tool itself just won't work anyway.
- The JSDoc way: Funny thing, some editors (again, like VSCode) use the
@type
of JSDoc to try to suggest the shape of some objects, and what's even more fun is that you can "import" TypeScript's declaration here!
For example:
/** @type {import("rollup-plugin-postcss").PostCSSPluginConf } */
const postcssOptions = {
plugins: /* you will get autocomplete here! */
}
Have fun writing safer code!
Editor: Please check bellow link for the configuration.
Hãy nói về Event Propagation
Context: Tại sao khi ta attach click handler vào một element và click vào child element bất kì của nó thì handler đó vẫn sẽ trigger? Sẽ ra sao nếu ta muốn nó không trigger mà chỉ trigger handler của chính target (child element) đó thôi? Đây là tình huống quen thuộc liên quan tới event bubbling mà bạn chắc đã gặp phải. Tuy nhiên, Event bubbling chỉ là một phần trong cơ chế propagation của event.
Introduction: Khi ta click vào một target, event propagation diễn ra qua 3 phase:
- Capturing: Browser đi từ element ở ngoài cùng và check xem nó có registere event handler nào cho capturing phase hay không và execute nó. Tiếp tục đi dần xuống cho đến ngay trước target.
- Target: Trigger handler ở target lần lượt theo thứ tự attach.
- Bubbling: Ngược lại với capturing phase, đi từ target's parent, browser check và execute handler cho bubbling phase và di chuyển dần lên đến ngoài cùng.
Usage:
Khi mọi người attach event handler vào một element thông qua handler property (btn.onclick
) hoặc dùng addEventListener
mà không specify gì thêm thì by default, mọi người đang register handler cho bubbling phase.
Để register handler cho capturing phase, ta chỉ cần set giá trị là {capture: true}
hoặc đơn giản shorthand là true
cho optional arg thứ ba của addEventListener
. E.g. btn.addEventListener("click", foo, true)
Note: Tại bất cứ thời điểm nào trong cycle này, nếu các bạn không muốn event tiếp tục propagate nữa thì có thể dùng Event.stopPropagation() , hoặc Event.stopImmediatePropagation() tùy nhu cầu.
Conclusion: Đến đây có thể các bạn sẽ thắc mắc: Why? :why: Chi cho phức tạp vậy? Ở thưở hồng hoang, một số browser chỉ implement capturing phase, một số chỉ có bubbling phase, nên khi standardize, các bác phải compromise implement cả hai. Nhờ có event propagation, thay vì phải attach handler lên rất nhiều element thì ta có thể delegate việc xử lí và coordinate event đó cho một element khác ở bên trên. Đối với cá nhân mình thì capturing phase mình không cảm thấy quá hữu ích, nhưng sẽ có một số niche use case có thể các bạn sẽ cần.
@thien
A little bit related: https://javascript.info/bubbling-and-capturing
@quannt
TLDR:
@Duy
Cũng nên nói kỹ một chút về sự khác biệt giữa target
và currentTarget
trong Event Propagation - Event Delegation
target
: là reference đến object mà event được dispatch
- Chỉ có 1 và duy nhất
- Là
deepest element
. Một trường hợp dễ xảy ra bug có thể thấy như: Mộtbutton
được attach handler, trong button là 1icon
và 1 đoạntext
. Khi click vàoicon
thì sẽ có target làicon
, click vàotext
thì sẽ có target là button - Không thay đổi trong quá trình propagation
currentTarget
: là reference đến object mà handler được attach (add)
- Sẽ thay đổi trong quá trình propagation, và cuối cùng sẽ về
null
- Vì thay đổi trong quá trình propagation, nên nếu dùng chúng trong các async callback như của
setTimeout
thì sẽ luôn nhận được giá trị lànull
. Có 1 workaround là lưu nó lại nếu muốn dùng tiếp sau này.
function handler(e) {
console.log(e.currentTarget) // EventTarget
const currentTarget = e.currentTarget
setTimeout(function(){ console.log(e.currentTarget) }, 0) // null
setTimeout(function(){ console.log(currentTarget) }, 0) // not null
}
Hãy nói về sự sống và cái chết :scream:
Bạn có biết, như con người, Node collection trong JS cũng có collection sống (live collection) và không sống (static/not live collection)?
Ủa wait, node collection là gì?
Khi gọi document.getElementsByClassName
hoặc document.querySelectorAll
chúng ta nhận lại được 1 array-like object chứa các elements mà 2 function đó tìm được, đó là node collection.
Sự khác bọt ở đây là document.getElementsByClassName
trả về HTMLCollection
và collection này là live collection. Còn document.querySelectorAll
trả về NodeList
và là static collection.
À hiểu roài, HTMLCollection
là live còn NodeList
là not live chứ giề :boss:
Ờm....đúng 50% roài. HTMLCollection
luôn luôn live, còn NodeList
thì lúc live lúc không.
Dafaq? thế khi nào NodeList
live khi nào không? :thinkhard:
Ờ....đôi lúc nó live, đôi lúc nó không...mình k biết. Tại vì hình như cũng không có cái central reference cho vụ này. Thôi thì cứ nhớ:
Node.childNodes
=> live
document.getElementsByName
=> live
document.querySelectorAll
=> not live
OK, roài behavior của live với not live là thế nào? :thinking_face:
Nếu 1 collection live, thì content của collection luôn up to date. Ngược lại, static collection thì content của collection không thay đổi.
Ví dụ:
Live collection:
const liveCollection = document.getElementsByClassName("box");
console.log(liveCollection.length); // 1
const div = document.createElement("div");
div.className = "box";
document.body.appendChild(div);
console.log(liveCollection.length); // 2
Static collection:
const staticCollection = document.querySelectorAll(".box");
console.log(staticCollection.length); // 1
const div = document.createElement("div");
div.className = "box";
document.body.appendChild(div);
console.log(staticCollection.length); // 1
À I see, thế tại sao phải quan tâm 1 collect có live hay không?
À thì các bạn thử đoán xem tại sao đoạn code dưới này gây ra infinity loop:
const allDivs = document.getElementsByTagName("div");
for (var i = 0; i < allDivs.length; i++) {
var newDiv = document.createElement("div");
allDivs[i].appendChild(newDiv);
}
Đậu xanh, rau má sao thấy hại không, có lợi chỗ nào đâu mà JS nó lại design kiểu này nhỉ? :arggg:
Thiệt ra thì live collection cũng có thể rất có lợi trong những tình huống như thế này:
// count number of online user in the user list
const onlineUserList = document.getElementsByClassName("online-user");
setInterval(() => {
console.log(`Number of online users: ${onlineUserList.length}`);
},
Bonus
Khi select = các API document.getElementBy*
thì performance luôn tốt hơn các API document.querySelector*
vì browser không cần phải match selector phức tạp và nhiều browser cũng implement optimization để matching các thứ đơn giản.
Ví dụ (nghe đồn chứ chưa kiểm chứng) là chrome có 1 internal hashmap để associate className với element nên khi gọi document.getElementsByClassName
thì thay vì traverse cả DOM để match tên class thì browser chỉ cần dùng input className làm key và access thông qua hashmap thôi.
Let's talk about Array sorting
Khác với các ngôn ngữ định kiểu (typed language), Array trong JS có thể chứa nhiều phần tử với nhiều kiểu dữ liệu khác nhau. Vì đặc tính này, các hàm như Array.prototype.sort
buộc phải chuyển từng phần tử về dạng chuỗi (string) khi sort, nên chúng ta sẽ có nhưng pha sort thần sầu như này:
let a = [1, 4, 3, 10, 11];
a.sort()
Expected:
a = [1, 3, 4, 10, 11]
Actual:
a = [1, 10, 11, 3, 4]
Mảng a
ở trên được sort theo thứ tự alphabet, mặc dù input của chúng ta là một mảng chỉ chứa toàn kiểu số.
TypeScript cũng không handle được case này.
let a: Number[] = [1, 4, 11, 3, 10];
a.sort(); // [1, 10, 11, 3, 4]
Cách giải quyết tất nhiên là phải viết custom comparator cho hàm sort:
a.sort((a, b) => a - b);
Nhìn qua thì có vẻ chẳng có gì để bàn cãi, nhưng đây thực sự là một hành động dư thừa không đáng có, khi một thao tác đơn giản như sort một mảng số theo thứ tự tăng dần, mà chúng ta cũng phải viết custom comparator cho nó.
Một cách giải quyết khác "đẹp" hơn, đó là sử dụng TypedArray. Ở đây, chúng ta assume các số trong mảng cần sắp xếp là các giá trị integer 32 bit:
let ta = new Int32Array([1, 4, 11, 3, 10]);
ta.sort(); // [1, 3, 4, 10, 11]
Sau khi xử lý dữ liệu với typed array xong, thì cần phải chuyển nó về lại dạng Array (vì không phải chức năng nào của Array cũng có trong TypedArray, ví dụ, không có push
, pop
, hàm isArray()
cũng sẽ trả về false
,...).
let a = [...ta];
// hoặc
let a = Array.from(ta);
A little bit better setInterval
Before:
setInterval(function foo() {
doStuff();
}, 1000);
After:
function foo() {
doStuff();
setTimeout(foo, 1000);
}
foo();
or, you know, use requestAnimationFrame :D
Why? To avoid event stacking (e.g. when browsers put your page into idle state and wake it up back. See: http://jsfiddle.net/s7uec8g1/ for an example)
Learn more:
Hãy nói về stacking context
Context: z-index
là một thuộc tính quen thuộc để specify thứ tự paint cho các element có overlap với nhau. Chắc hẳn các bạn đã hoặc sẽ gặp phải trường hợp chỉ dùng z-index thôi không đạt được behaviour mà bạn mong muốn. Hoặc bạn đã và sẽ gặp việc có tồn tại quá nhiều z-index
value trong code base và z-index
cứ được tăng dần đều mỗi khi bạn muốn đảm bảo rằng một element nào đó phải nằm trên những element khác. Khi hiểu về stacking context, ta sẽ có phương án để xử lí (và quản lí) tốt hơn cho các tình huống trên.
Introduction: Trước khi nói về stacking context thì ta phải nói về paint order. Paint order đầy đủ của spec khá phức tạp. Trong phạm vi topic hôm nay, mọi người chỉ cần hiểu và nhớ quy luật sau:
- Trong cùng một stacking context, element có
z-index
value thấp hơn sẽ được paint trước, nếuz-index
value bằng nhau thì sẽ paint theo thứ tự xuất hiện trong source. - Nếu một element tạo ra một stacking context mới (tạm gọi là stacking container) thì toàn bộ children của nó sẽ được paint ngay lập tức sau đó, mà không consider đến sibling của container đó -> Điều này có nghĩa là
z-index
value của các children element ở stacking context khác nhau sẽ không có ảnh hưởng lẫn nhau trong việc quyết định paint order -> Paint order lúc này sẽ phụ thuộc vào paint order của container.
Ứng dụng: Như vậy có thể thấy ngay là ngoài z-index
, ta có thể manipulate paint order bằng cách tạo ra stacking context mới (qua nhiều cách khác nhau) (nếu bạn từng gặp behaviour lạ khi dùng z-index
thì nguyên nhân rất có thể là bạn để vô tình tạo stacking context mới).
Ở trong stacking context đó, z-index
value sẽ được isolate, không chịu ảnh hưởng từ thế giới bên ngoài (nguyên nhân đã nói ở trên)
-> Như vậy, nếu mỗi sibling element/component tạo ra stacking context riêng thì khi compose chúng với nhau, chúng ta chỉ cần quan tâm đến z-index
và paint order của chính chúng. Đại chiến z-index
giữa tám triệu elements khác nhau đến đây là kết thúc :raised_hands: :tada:
(Tuy nhiên, các bạn sẽ vẫn cần cân nhắc tradeoff trong việc khi nào nên tạo stacking context mới; và bên trong mỗi isolated component đó ta sẽ vẫn cần strategy để nó không quay trở lại bài toán ban đầu)
Hãy nói về Subresource Integrity
Context
Người ta thường hay nhớ về "the good old days", khi mà FE development chỉ cần include một cái script hay link của một cái library nọ, được serve qua một cái CDN kia. Trong cái tiện có cái hiểm nguy. Sẽ như thế nào nếu file ở CDN đó bị compromise? Trên tinh thần thì resource nào mình tự host và kiểm soát được thì không nên trông cậy vào bên ngoài. Tuy nhiên nếu lỡ bạn có bắt buộc phải "đi đêm", thì browser cũng có đồ bảo hộ cho trường hợp này, đó là Subresource Integrity.
Cách dùng
script
và link
tag nhận một attribute là integrity
, có giá trị là một chuỗi, chứa phần base64-encoded hash ở cuối, và prefix phía trước bằng hashing algo được dùng. Ví dụ: sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC
. Các bạn có thể dùng sha256
, sha384
, hoặc sha512
. Nếu để ý các mọi người sẽ thấy nhiều popular library (như Bootstrap) sẽ có include sẵn hash này vào official CDN link mà họ share. Nếu không có thì bạn sẽ phải check file integrity ở CDN đó bằng cơm rồi tự generate hash.
Khi gặp phải resource có integrity check, browser sẽ fetch, tạo hash từ file đó và compare với hash value mà bạn set. Nếu check fail (chứng tỏ file đã bị thay đổi), browser sẽ trả về network error
(Lưu ý rằng crossorigin
bắt buộc phải được set nếu bạn đang request từ một origin khác, nếu không integrity check sẽ không hoạt động)
Note: Ngoài dùng cho 3rd party resource, bạn hoàn toàn có thể apply integrity check cho owned resource, phòng khả năng chính bạn bị partially compromised.
Let's talk about CSS Variables
CSS Variables, hay còn gọi là CSS Custom Properties, là các thuộc tính CSS có dạng --*
, có chức năng lưu giữ các giá trị trong CSS.
Các giá trị này khi cần, có thể được đọc ra bằng hàm var()
, giá trị trả về từ hàm var()
này có thể được dùng để gán cho các CSS Properties khác.
// define
--foo: #ff0000;
// read and reassign
color: var(--foo);
--new-foo: var(--foo);
CSS Variables là case sensitive, nên --foo-bar
sẽ khác với --Foo-bar
và khác luôn với --foo-Bar
.
Tính kế thừa của CSS variables thì giống với mọi properties khác nên không có gì để nói đến. Có thể hiểu nôm na là, một CSS variable được define/hay thay đổi trong một scope, thì mọi scope con của nó đều có thể access đến giá trị của variable đó.
Nếu chúng ta sử dụng hàm var()
để lấy giá trị của một CSS variable chưa được define, thì có thể bỏ thêm một tham số vào để làm fallback value, ví dụ:
var(--something-not-defined, 10) // giá trị sẽ là 10
--something-defined: 50;
var(--something-defined, 10) // giá trị sẽ là 50
Lưu ý, nếu browser không hỗ trợ CSS Variable thì xài fallback value cũng vô dụng.
Một case quan trọng nhất đó là, sẽ thế nào nếu bạn sử dụng sai kiểu dữ liệu cho một variable? Ví dụ một variable có giá trị số, nhưng gán vào cho một property có giá trị màu (như color
, hay background-color
)?
Ví dụ:
:root { --text-color: 16px; }
p { color: blue; }
p { color: var(--text-color); }
Ở đây biến --text-color
mang giá trị số (16px) nhưng được gán cho thuộc tính color
của thẻ <p>
. Là sai lè lè.
Browser nó sẽ làm như này (#1):
- Đầu tiên, nếu gặp giá trị không hợp lệ, nó sẽ lấy giá trị inherit, tức là giá trị được kế thừa từ level cao hơn. Nhưng thẻ
này không có style nào được set ở level cao hơn.
- Nên nó sẽ lấy giá trị
initial
mặc định, là màu đen.
Cuối cùng, giá trị trả về của var(--text-color)
trong trường hợp này sẽ là màu đen (có thể nói CSS cũng có type inference :kappa:). Nên chốt lại, màu chữ của thẻ
trong trường hợp này sẽ là màu đen.
Một số debugging tricks (debug nhé, đừng áp dụng vào code thật)
Freeze browser in 3, 2, 1 (e.g. to debug UI appear on hover of another element)
window.setTimeout(() => { debugger }, 3000)
Log anywhere (e.g. in the middle of a JSX)
<div>
... // in a very nested tree, maybe having some loop
<Button
color={(() => { console.log(foo); return colors.red; })()}
/>
...
</div>
Break on property access (e.g. the title is changed but by whom!?)
const org = document.title;
Object.defineProperty(document, "title", {
get: function () {
debugger;
return org;
},
set: // you know the idea
})
Just a nice table in console
console.table(/* array of arrays */)
(actually just learn console, there are a lot of useful utils like console.table, console.important, console.group, etc.)
Print your complex state, beautifully, in the middle of your JSX:
<pre>{JSON.stringify(complexState, null, 2)}</pre>
Slow down animation when you don't have the dev tool
* { animation-duration: 10s !important; }
@quannt
Nếu dùng Chrome thì cái Force state và Break on này cũng tiện lắm nè.
Hãy nói về resolution switching problem
Đây là vấn đề còn lại gặp phải khi deal với responsive image (art direction problem đã nói ở lần trước). Do browser sẽ tiến hành fetch image trước khi CSS hay JS kịp được parse (ngay cả trước khi DOM được fully constructed), nên ta phải deal với nó ngay ở markup.
Browser cung cấp cho chúng ta hai để giải quyết:
srcset
Như tên gọi, đây là một set các image source, cùng với miêu tả (descriptor) về điều kiện sử dụng của chúng. Qua đó browser sẽ có thể pick image tối ưu nhất cho người dùng. Trong descriptor, chúng ta lại có 2 loại:
- Density descriptor. Ví dụ:
srcset="foo.jpg 1x, foo-large.jpg 2x"
. Dùng để adapt cho các thiết bị có pixel density cao. Tuy nhiên có thể thấy ngay một hạn chế của descriptor này là không adapt được khi actual width của image thay đổi khi ở các screen size khác nhau. - Width descriptor. Ví dụ:
srcset="foo.jpg 480w, foo-large.jpg 1280w
. Ở đây chúng ta cho browser biết độ lớn thực tế của bản thân mỗi source mà ta có. Nếu chỉ dừng lại ở đây thì hoàn toàn vô ích. Vì với density descriptor, browser đã biết được trước pixel density của device để có thể pick source tối ưu nhất. Với width descriptor, chúng ta phải cung cấp một dữ kiện nữa là độ lớn cần có của image (khi render), thông quasize
attribute.
size
Việc specify responsive size ở markup cũng không khác ở CSS mấy. Chúng ta sẽ specify một list các media query và độ lớn của nó khi thỏa mãn media query đó. Ví dụ sizes="(min-width: 1280px) 33.3vw, 100vw"
(surprisingly, ta còn có thể dùng cả hàm calc trong đó). List này được evaluate từ trái sang phải, giá trị cuối cùng là default.
Kết hợp scrset
width descriptor và size
, browser đã có đủ đồ chơi để pick đúng ảnh phù hợp nhất.
Và đây là thành quả
<img
srcset="large.jpg 1280w, medium.jpg 640w, small.jpg 320w"
sizes="(min-width: 1280px) 33.3vw, 100vw"
/>
Note 1: Còn một vấn đề nữa là type switching problem, nhưng cái đó có thể xử lí dễ dàng bằng picture
element y như cách chúng ta xử lí art direction switching vậy. Mọi người tự tìm hiểu thêm)
Note 2: Ngoài ra còn 1 try hard mode là làm sao để take into account cả art direction, width, screen width và pixel density. Nếu phải đến bước này thì chúc các bạn may mắn, bình an, mạnh dỏi và minh mẫn
Let's talk about Accessibility (A11y)
Một trang web đạt chuẩn accessibility khi nó được thiết kế và phát triển để giúp cho mọi người đều có thể sử dụng được, kể cả những người già, những người khuyết tật hay đơn giản là những người không sử dụng các thiết bị input thông thường trên máy tính.
Sử dụng được ở đây có nghĩa là:
- Có thể tiếp nhận được nội dung trên trang web (ví dụ người khiếm thị vẫn có thể nghe được nội dung văn bản, hình ảnh, hoặc người khiếm thính vẫn có thể đọc được các nội dung âm thanh,...)
- Có thể navigate được, và tương tác được với nội dung trên trang web (người không dùng chuột thì vẫn có thể navigate bằng bàn phím, người khiếm thị vẫn có thể navigate hoặc nhập liệu được bằng giọng nói... ví dụ thế)
Tất nhiên accessibility vẫn có thể đem lại rất nhiều lợi ích cho những người không mang khuyết tật, ví dụ keyboard navigation, hoặc người nào thị lực kém, vẫn chỉnh được chữ to lên, high constrast hơn, ai xài kết nối internet kém vẫn có thể sử dụng được trang web mà không gặp trở ngại.
Nếu chỉ có ý định support accessibility một cách cơ bản, bạn có thể focus vào các yếu tố sau:
- Đừng thay đổi thuộc tính
tabIndex
của một element nếu không cần thiết - Đừng disable cái focus outline của một element (repeat after me:
outline: none
trong CSS là một tội ác) - Nếu phải disable focus outline vì nó quá xấu, thì phải design một cái outline mới đẹp hơn và rõ ràng hơn để bỏ vào
- Sử dụng semantics HTML tags như
<article
>,<main>
,<nav>
,... nếu có thể - Với các input element, nên đặt thuộc tính
role
một cách rõ ràng và chính xác - Sử dụng element đúng với mục đích của nó, ví dụ, không dùng thẻ
<div>
để làm nút bấm (button) - Sử dụng các thuộc tính
ariaảia-*
nhưaria-label
,aria-labelledby
,aria-descibedby
,... để chú thích và chỉ ra mối quan hệ cho các nội dung/element trên trang web Hiện tại, các hệ điều hành và các trình duyệt đữa đưa ra rất nhiều tiện ích để hỗ trợ accessibility, như là screen readers (đọc nội dung của trang web dựa vào các thuộc tínharia-*
, trên MacOS có VoiceOver, trên Windows phải sử dụng các ứng dụng của bên thứ 3 như JAWS), hay các giải pháp điều khiển máy tính thông qua mắt nhìn (built-in của MacOS),... tất cả những giải pháp này đều phụ thuộc rất nhiều vào tiêu chuẩn WCAG. Có thể tham khảo thêm các tài liệu sau đây về accessibility: - Một vài bước kiểm tra accessibility đơn giản (https://www.w3.org/WAI/test-evaluate/preliminary/)
- Tiêu chuẩn Web Content Accessibility Guidelines (WCAG) https://www.w3.org/WAI/WCAG21/quickref/ Bên cạnh đối tượng user là những người bình thường, lành lặn, đầy đủ cả tay chân tai mắt mũi mà chúng ta vẫn tưởng tượng ra hằng ngày, ngoài kia vẫn còn rất nhiều người kém may mắn hơn, và họ vẫn có điều kiện để tiêp xúc với công nghệ mỗi ngày, và những user như họ cần được hỗ trợ nhiều hơn từ phía những người trực tiếp làm ra sản phẩm, là frontend developer chúng ta, cho nên, hãy bỏ chút thời gian và công sức để giúp đỡ những user đặc biệt này, mình chắc chắn là bạn sẽ thấy công việc của mình có nhiều ý nghĩa hơn. Đặc biệt, đối với những frontend developer đang làm việc tại Mỹ, thì luật pháp quy định mọi trang web đều phải accessible, nên không support hoặc không quan tâm đến a11y có thể coi là phạm pháp.
Quick note về khái niệm origin trong JavaScript
Một URL thường sẽ có cấu tạo như sau:
<scheme>://<host>:<port>/<path>
ví dụ
http://localhost:45848/hello
https://thefullsnack.com
Một tập hợp của scheme
, host
và port
sẽ định nghĩa thành một origin
. Vậy cho nên khi nói hai nội dung có cùng origin tức là chúng nằm trên cùng scheme + host + port, và tất nhiên nếu một trong 3 yếu tố trên khác nhau thì chúng ta có 2 nội dung không nằm cùng origin với nhau.
Riêng IE, với phong cách nổi loạn thường thấy, sẽ bỏ qua port khi xét origin, nên 2 URL có cùng scheme + host mà khác port thì vẫn tính là same origin.
Ví dụ về 2 URL có cùng origin:
https://something.com/hello
https://something.com/yolo
Ví dụ về các URL không có cùng origin với nhau:
// Khác scheme
http://abc.com/bobo
https://abc.com/koko
// Khác host
https://foo.abc.com
https://bar.abc.com
// Khác port
https://abc.com
https://abc.com:8443
Phân biệt được sự khác nhau về origin có thể giúp bạn hiểu và tìm ra giải pháp dễ dàng hơn khi gặp những vấn đề liên quan đến CORS (cross-origin resource sharing), khi truy xuất localStorage, hay khi tìm hiểu về execution context, event loop,...
Đối với các thao tác liên quan đến network connection trên một trang web (HTTP request, hay load image), việc truy xuất cross-origin cho các thao tác sau đây được cho phép:
- Redirect, link, submit form
- Embedding (như inject content dùng thẻ
<script>
,<link>
,<img>
,<video>
,<audio>
,<object>
,<embed>
,<iframe>
, load font dùng@font-face
,...)
Đối với Cookie, thì khác subdomain vẫn được tính là cùng origin.
Hãy nói về art direction problem
Deal với responsive image là một vấn đề đau đầu, vì browser sẽ tiến hành preload image trước khi js hay css có cơ hội được parse và kicks in. Trong topic này có 2 vấn đề lớn. Hôm nay mình sẽ warm up bằng cách nói về vấn đề dễ xử hơn trong đó là: art direction problem.
Context
Giả sử các bạn có một full-width banner, photo đã được set bố cục thích hợp để nhường đủ không gian và hiển thị headline và tagline một cách nổi bật nhất. Sẽ như thế nào nếu user vào trang đó bằng mobile device? Khi bức ảnh đó bị scale down hoặc crop thì chắc chắn bố cục của bức ảnh đó sẽ không còn phù hợp nữa. Bạn sẽ cần một version khác được thiết kế chuyên biệt cho mobile screen. Đây chính là art direction switching,
Solution
Vấn đề này có thể được xử lí đơn giản bằng cách xử dụng picture
element. Như sau:
<picture>
<source media="(max-width: 799px)" srcset="foo-480w-closeup.jpg">
<source media="(min-width: 800px)" srcset="foo-800w.jpg">
<img src="foo-800w.jpg">
</picture>
Browser sẽ evaluate lần lượt từng source
theo thứ tự, nếu media
condition của source
đó fail thì nó sẽ skip tới source
tiếp theo. Khi không có source
nào match, hoặc browser không support picture
thì nó sẽ display cái default img
ở cuối.
Nếu để ý code block trên, các bạn sẽ thấy trong source
ta dùng attribute là srcset
thày vì src
. Đây là một trong những bước mà ta phải làm để deal với resolution switching problem sẽ được nói ở đợt sau. (edited)
Hãy nói về contain CSS property
Context
Hôm trước mình có nói về việc hạn chế trigger nhiều phase của rendering pipeline khi animate để có performance tốt nhất. Trong trường hợp bạn bắt buộc phải trigger các phase tốn kém, không thể tránh khỏi, browser có cung cấp thêm cho bạn property là contain
để giảm thiểu được khối lượng việc phải làm, bằng cách cung cấp early hint rằng những thay đổi những ra trong subtree của element/area được set đó sẽ không làm ảnh hướng đến những thành phần khác trong page và ngược lại (lí do vì sao nó tên là contain
). Ngoài ra, với các dữ kiện này, browser còn có thể apply thêm các optimization khác mà nó thấy phù hợp.
Usage
Có ba giá trị chính bạn có thể set cho contain
là layout
, paint
và size
. Nếu nhớ post trước về rendering pipeline, các bạn sẽ nhận ra layout
và paint
là hai phase quen thuộc trong đó.
Với layout
, những thay đổi bên trong containment area sẽ không khiến browser phải recalculate lại toàn bộ layout object như trước. Tuy nhiên, nếu element đó resize và làm thay đổi direct ancestor của nó thì đó sẽ được tính như thay đổi ở bên ngoài, không được contained, và do đó sẽ bắt buộc browser tính toán lại layout như bình thường. Một ví dụ ứng dụng là bạn set offet top
, right
cho absolute positioned element. Viêc này thường không làm thay đổi height hay width của container, nhưng lại trigger layout phase trong rendering pipeline. <-- perfect use case cho layout containment.
Với paint
, bất cứ elements nào nằm trong subtree của containment area sẽ nằm gọn trong đó về visual. Những phần vượt ra ngoài boundary (ví dụ như floated element) sẽ bị cắt bỏ. Điều này còn có nghĩa là ta còn đang cung cấp một guarantee cho browser rằng, nếu container đó nằm ở offscreen, thì tất cả con cái của nó cũng vậy -> Browser có thể skip hoàn toàn paint phase cho chúng.
Với size
, thông thường không quá hữu dụng. Chỉ là một hint nữa cho browser, rằng element này có fixed dimension. Đồng thời khi set size
, ta cũng sẽ bị bắt buộc phải specify chính xác dimension vì responsive, auto-sized element sẽ bị collapse như thể nó hoàn toàn không có content.
Side effects
layout
và paint
value sẽ có cùng side effects/pitfalls mà bạn sẽ cần lưu ý.
- Nó sẽ tạo ra formatting context mới (đã nhắc đến trong post trước)
- Nó sẽ trở thành containing block cho positioned elements
- Nó sẽ tạo ra stacking context mới (liên quan đến
z-index
nếu bạn nào chưa biết, có thể sẽ chia sẻ trong post khác)size
không gây ra các side-effects trên
Ngoài ra chúng ta còn có hai value nữa là strict
, content
, nhưng thật ra đây chỉ là short hand cho combination giữa 3 giá trị trên
(Wait, thật ra còn một value nữa là style
, nhưng không nên dùng vì không được widely support và có khả năng bị removed khỏi spec)
Hãy nói về Block Formatting Context (BFC)
Context: Hôm trước mình có đề cập display: inline-block
là shorthand của display: inline flow-root
. Hiểu đơn giản nhất thì flow-root
là cách để establish một BFC mới. Nghe có vẻ lạ nhưng trên thực tế nếu làm việc với CSS, các bạn chắc chắn đã gặp phải BFC vì rất nhiều giá trị của CSS tạo ra BFC by default. display: flow-root
là cách chính thống để chúng ta explicitly tạo ra BFC mới.
Định nghĩa: Formatting context mọi người có thể hiểu là một context/area mà trong đó content sẽ được layout theo rule của nó. Mọi người sẽ thấy điều này rõ hơn nếu quan sát hai người em sinh sau đẻ muộn của BFC là Flex formating context và Grid formatting context. Bản thân <html>
tag tạo ra một cái BFC to bự bao trùm tất cả, đây là lí do vì sao chúng ta có block box layout by default thay vì là flexbox.
Ứng dụng: BFC thường hoạt động ngầm bên dưới, không phải là cái bạn phải đụng đến hàng ngày. Nhưng một BFC có một số đặc tính thú vị sau:
- Nó sẽ contains float. Có nghĩa là nó sẽ tự expand để bao trọn float item, không để nó vượt ra bên ngoài box boundaries. Để xử lí vấn đề này thường chúng ta phải dùng clearfix hack. Đây là một cách khác.
- Nó sẽ không bị ảnh hưởng bởi external float. Có nghĩa là nó sẽ không wrap float item ở bên ngoài.
- Nó sẽ ngăn margin collapsing. Margin của bên ngoài không ảnh hưởng đến element ở bên trong và ngược lại.
Một trong những hack các bạn của thể thấy trước đây là dùng
overflow: auto
. Under the hood nó sẽ tạo ra một BFC mới. Để biết chi tiết những trường hợp nào tạo ra BFC, xem ở đây
FAQ: Một block-level element bản thân nó có tự tạo ra BFC không? -> Không nhất thiết. Cũng giống như việc một flex item (nằm trong flex formatting context) không phải là flex container vậy, trừ khi chúng ta specially set thêm cho nó
@ZeroX Vắng bóng quá nên mình share thêm 1 vài điều thú vị về BFC:
<html>
không tựdisplay: block
mà là do được cấu hình sẵn by default = user agent stylehsheet. User agent stylesheet là 1 file CSS đặc biệt được inject bởi browser by default. Đó là lý do các bạn xài reset.css để override cái user agent stylesheet.
Tham khảo user agent stylesheet của chrome:
https://chromium.googlesource.com/chromium/blink/+/master/Source/core/css/html.css
2. 1 element sẽ "establish" 1 BFC nếu có ít nhất 1 element con là "block-level element". Nếu tất của element con đều display: inline
thì element hiện tại (dù display: block
) sẽ establish inline formatting context thay vì BFC.
Tham khảo spec: https://www.w3.org/TR/CSS22/visuren.html 3. Làm sao biết 1 element là block-level element? Câu trả lời năm trong spec trên:
Values of the 'display' property that make an element block-level include: 'block', 'list-item', and 'table'. => dù element
display: inline-block
bản thân nó là inline element nhưng lại có thể establish BFC. Vì để establish được BFC, nó không phụ thuộc vào element có phải là block-level hay không mà phụ thuộc vào là element đó có generate block-container box khi layout hay không.
Again, block-container box là gì? well....đọc spec trên các bạn sẽ hiểu...hoặc không....tại vì mình không....và mất pà nó 3 tháng ngâm cứu mới hiểu.
@Cậu Làm Vườn
Để dễ hình dung mối quan hệ giữa block và BFC lần nữa vì concept này khá confusing: display: block
là shorthand của display: block flow
. Nếu không rơi vào trường hợp browser tự tạo BFC thì muốn block level element đó tạo BFC, bạn phải specify display: block flow-root
. Ở một ví dụ khác, display: flex
là shorthand của display: block flex
, chính cái giá trị inner display nó tạo ra flex formatting context và biến những elements trong đó thành flex items.
Làm thế nào để có performant animation?
Context: Mọi người đều đã từng gặp qua những trang web có phần visual và animation đẹp, nhưng lại làm cho browser của người dùng nấc cục liên hồi. Ở đây mình sẽ nói về việc làm thể nào để animation nhẹ nhàng, tạo ra ít việc cho browser nhất có thể. Để hiểu cách xử lí vấn đề này, hãy cùng xem lại rendering pipeline của browser.
- Life of a pixel: Sau khi có được render tree, browser sẽ qua các bước: 1) tính toán và construct layout object -> 2) record lại paint operations cần được thực hiện -> 3) raster (transform) paint ops ở phase trước thành color bitmap -> 4) gpu/rendering engine call để render ra pixel. Việc gì sẽ xảy ra khi mình thay đổi một thuộc tính CSS nào đó? That's right, browser sẽ phải thực hiện lại một phần hay toàn bộ quy trình trên.
Kết luận 1: Nếu thay đổi của mình chỉ trigger những phase càng về sau thì browser sẽ phải thực hiện ít việc hơn. Các bạn có thể tham khảo ở đâyđể biết css property nào sẽ trigger cái gì.
- Layers:
Nếu click vào link trên, mọi người sẽ gặp một "phase" không có để cập ở trên là
composite
Hãy tưởng tượng mình vẽ một bức tranh bầu trời xanh, có vài đám mây trắng. Sau khi vẽ xong, mình đổi ý muốn đám mây đó nằm ở bên phải thay vì ở giữa. Khả năng là mình sẽ phải xé nháp vẽ lại bức tranh mới. Thay vào đó, nếu ban đầu mình vẽ đám mây đó trên một mảnh giấy khác, thì chúng ta có thể dễ dàng sắp xếp, thay đổi vị trí của nó trên nền bức tranh bên dưới. Đây là simplified concept của compositor layer Việc compositing (kết hợp) các layer được thực hiện ở một thread riêng biệt với main thread (gọi là... compositor thread). Điều này có nghĩa rằng những tác vụ trên đó sẽ không chịu ảnh hưởng từ việc main thread bị clogged up (do javascript chẳng hạn). Kết luận 2: Nếu mình có thể tách những elements cần được animate ra một compositor layer riêng thì animation sẽ được thực hiện tốt và ít tốn kém hơn. Trên thực tế thì mỗi khi dùng CSS transitions và CSS animations, browser đã tự động thực hiện thao tác tạo layer mới. Tuy nhiên, khi phải animate và thực hiện interpolation giá trị ở js, ta phải tự thân vận động
TLDR: là mọi người không cần hiểu chính xác under the hood nó hoạt động thế nào. Chỉ cần lưu ý
- Animate bằng property trigger càng ít phase càng tốt (ví dụ dùng
transform: scaleX()
thay choheight
. - Dùng CSS transition và animation nếu có thể; nếu phải animate ở js thì hãy xem bài trên để biết cách force layer; or dễ hơn nữa, pick 1 cái animation library tốt, họ sẽ handle việc đó giúp bạn.
Về connection trong HTTP/1.X
Có 3 phương pháp thường được dùng: Short-live connections, Persistent connection và Pipelining (ảnh)
- Short-live connections: default của HTTP 1.0, mỗi request lên server sẽ mở một HTTP connection mới. Do HTTP connection về bản chất là TCP connection ở dưới nên với mỗi một short-live connection, sẽ cần tốn thêm 2 requests để mở connection (2 request này sẽ khiến performance của trang web chậm đi trông thấy do khoảng cách địa lý giữa các server và giới hạn tốc độ của ánh sáng, rất khó để optimize)
- Pros: đỡ tốn tài nguyên từ phía server (?).
- Cons: chậm, mỗi request phải gánh thêm 2 requests con để mở TCP connection. Ví dụ trang web load 10 file javascript từ server sẽ ngốn thêm 10 x 2 = 20 extra requests.
- Persistent connection (aka keep-alive connection): default của HTTP 1.1, tương tự như short-live, nhưng thay vì đóng connection ngay sau khi gửi xong request thì sẽ giữ connection mở và dùng lại connection đó cho các request sau.
- Pros: nhanh hơn Short-live connection vì ko tốn thời gian mở TCP connection cho mỗi request.
- Cons: Ngốn tài nguyên server (do phải maintain connection), vì chỉ có một request nên effectively mọi request sẽ phải xếp hàng và hoạt động như một FIFO queue, nếu trang web có nhiều resource thì có thể xảy ra trình trạng nghẽn cổ chai nếu một request quá lớn hoặc tắc (trong thực tế thì modern browser workaround cái này bằng việc mở tối đa 6 TCP thay vì 1, dev thì thường work around bằng việc dùng domain sharding để tăng số TCP connection lên nhiều hơn 6)
- Pipelining: về bản chất là vẫn là Persistent connection nhưng có thêm cải tiến nhỏ: thay vì để client tự sắp xếp và tự queue connections thì sẽ làm việc đó ở phía server, rất tiếc là vì nhiều lý do chủ quan và khách quan nên support cho Pipelining rất hạn chế và hầu như hiện tại ko có giá trị sử dụng trong web dev. tldr: cost của việc đóng mở HTTP connection rất lớn, cộng với việc hạn chế của bản thân protocol HTTP nên trên thực tế đa số các cách optimize cho HTTP 1.1 đều tập trung vào việc giảm thiểu cost cho HTTP connection: ví dụ preconnect, domain sharding, dùng CDN, sprite ảnh, etc
Kì sau: HTTP 2 và thay đổi với web dev
Reference:
Tools hay cho SVG
Lúc đầu định nói về SVG nhưng tự thấy mình biết ít quá nên thôi để tập trung chia sẻ 2 tool mình rất thích khi làm SVG trên web, tool đầu thì mình xài từ lúc bắt đầu đi code FE, tool sau thì gần đây mới biết và cũng xài liên tục
- SVGOMG https://jakearchibald.github.io/svgomg/(chắc nhiều bạn nhận ra tác giả nhỉ :D)
Tool này thì ngắn gọn đơn giản dễ hiểu là format SVG. Tại sao lại cần như thế? Vì SVG là một định dạng rất đặc biệt, vừa machine-readable, vừa cần human friendly:
- Người thiết kế ra SVG, có thể là logo hay icon, thường sẽ sử dụng tool GUI, ví dụ Figma hay Sketch. Những tool này sẽ generate ra SVG files
- Vấn đề là, FE dev tụi mình cũng sẽ có nhu cầu edit trực tiếp SVG. Đương nhiên là bọn mình sẽ k ngồi chỉnh path bằng tay, nhưng bọn mình sẽ có thể muốn đổi fill color thành "currentColor", hay chuyển từ width height sang viewBox đúng không?
- Cho nên thường khi cần sử dụng 1 SVG từ file thiết kế thì mình sẽ run qua SVGOMG (btw try to pronoun it, it's one of the best naming in this industry) để có 1 cái human-friendly source of code, và mình bỏ cái này vào codebase của mình
Đây là tool giúp ích rất nhiều khi bạn nhận ra vấn đề của SVGOMG là nó chỉ áp dụng cho từng trường hợp. What if bạn có 100 icons từ file Figma của designer? What if bạn đang build 1 UI kit và UI kit của bạn muốn support nhiều icon set, trong đó mỗi icon set lại có vài trăm icon? SVGR giải quyết vấn đề này:
- Về bản chất thì SVGR làm 2 thứ, 1 là cleanup cái SVG bằng svgo (là cái underlying tool mà SVGOMG cũng dùng), 2 là biến cái SVG đã clean đó thành 1 react component
- Tại sao việc biến thành react component lại hay ho? Vì code splitting/tree shaking. Mọi người cũng biết là có rất nhiều cách để dùng SVG, (quá nhiều, đúng không?) thì trong số đó cách có ít compromise nhất mình thấy là biến nó thành React components, hoặc at least là JS/TS source of code, vì đây là cách dễ nhất cho các build tool (webpack, rollup) hoặc framework (nextjs, CRA) được setup để làm code splitting out of the box.
- Và đừng quên là SVGR k phải là 1 tool GUI như SVGOMG, mà là 1 node app, tức là bạn có thể integrate vào build process của bạn
Hãy nói về line-height
Thật ra topic này quá deep (và f*ck up) để có thể tóm tắt dưới dạng bite-size cho mọi người. Blog post này sẽ trả lời kĩ hơn cho bạn câu hỏi line-height
sẽ được tính toán ra sao.
Tuy nhiên, có một số điểm thú vị mình sẽ cố tóm tắt lại, giả sử ta set font-size: 100px
:
- Font char được vẽ trong một cái box gọi là em-square
- Khi ta set
font-size: 100px
, cái chúng ta đang set là em-square của nó - Em-square không phải là cái hard limit cho chiều cao của font, mà hoàn toàn phụ thuộc vào cách mà font được define, content area có thể vượt ra ngoài hay nằm lọt thỏm - trong em-square đó.
- Ý trên là lí do computed height (content area) sẽ không chính xác bằng 1
em
(100px) - Default value của
line-height
lànormal
. Chiều cao thật sự của line lúc này sẽ được tính bằng:content area
+line gap
(do font tự define). - Nếu chúng ta set
line-height: 1
, chiều cao thật sự của line có thể sẽ nhỏ hơn chiều cao của content trong đó (vì đã nêu ở trên, content area có thể sẽ lớn hơn 1em) vertical-align
cũng sẽ có ảnh hưởng đếnline-height
.- Beware of the invisible: vô tình set multiple font trong cùng một line sẽ f*ck up line height của bạn. Ví khi tính baseline alignment, browser sẽ consider tất cả, cho dù visually nó không hiện diện.
Kết luận chính nhất là cái mà bạn nhận được trên màn hình phụ thuộc rất nhiều vào font spec. Bạn có thể nhảy qua tám triệu cái hoop để có thể làm cho font symbol cao ở con số chính xác mà bạn muốn. Nhưng mà đến đây thì có lẽ sẽ tốt hơn nếu bạn reconsider lại cái nghề này và mua một mảnh ruộng về cày cắm.
@minh minh
fact: trong thiết kế đồ họa, line-height được gọi với một cái tên khác là bouncing box (dĩ nhiên bao gồm cả letter-width).
line-height: 1
có nghĩa là chiều cao dòng đó bằng với cái font size. Giá trị line-height có thể dùng giá trị số (không cần đơn vị, khác với hầu hết các thuộc tính khác của css), với giá trị x thì chiều cao dòng bằng x nhân với kích thước phông chữ tại vị trí đó.
@Cậu Làm Vườn
Line height trong bối cảnh ở frontend thì sẽ bao gồm cả phần leading (line gap) giữa 2 line. Khi set fixed value thay vì default thì line height tươn đối preditable, cái khó predict là phần leading đó, vì content area của font có thể sẽ to hay nhỏ hơn em square, nên visually, cũng font-size, cùng line height mà mỗi font sẽ cho ra cảm nhận khác nhau.
Hiểu về cascading trong CSS.
Bạn có biết chữ "C" trong CSS là Cascading? Vại Cascading là gì?
Context: Ví dụ có 1 div với id box và các style rules như sau:
div { color: black; }
#box { color: blue; }
Cả 2 rules đều match với div
nói trên, vậy color
của div
sẽ là black
hay blue
?
Đây là lúc browser sẽ sử dụng cascading để tìm ra value được sử dụng. Cascading thực chất rất đơn giản, sort list các value được khai báo trong style rule từ lớn đến bé và lấy value lớn nhất.
Ví dụ: chúng ta có 1 list 2 value là black
và blue
, để sort 2 value này thì browser đầu tiên sort theo origin và importance. Tức là value có !important
sẽ lớn hơn value không có. Và value có origin lớn hơn sẽ được ưu tiên trước.
Nếu 2 value đều bằng nhau thì browser sẽ sort theo cách thứ 2: theo specificity của selector. Nếu là fan ruột thịt của @huy các bạn sẽ nhớ bài này.
Nếu 2 value vẫn bằng nhau thì thằng nào khai báo cuối cùng sẽ được chọn.
Vì vậy lần sau thay vì xài !important
everywhere thì bạn nên tăng specificity cho selector.
Ở đây mình nói sơ lược thôi, còn các bạn muốn biết đầy đủ hơn thì nên đọc bài blog mới nhứt của mình
https://zerox-dg.github.io/blog/2021/01/13/Browser-from-Scratch-CSS-parsing-processing/
Hiểu về display property
Context: Khi làm việc với CSS, các bạn sẽ gặp các value quen thuộc cho display là block
, inline
, inline-block
, grid
, flex
, etc. Nếu tinh ý thì các bạn sẽ nhận ra rằng block
và inline
element chẳng hạn, sẽ không có special behaviour dành cho children của chúng, trong khi grid
item và flex
item được xử lí một cách đặc biệt.
Nguyên nhân là do cái các bạn đang thấy thật ra chỉ là short-hand value. Ở CSS3, display
nhận 2 value: outer (define cách chúng tương tác với các element xung quanh) và inner (cách children của chúng tương tác với nhau).
Ví dụ:
display: block
là shorthand củadisplay: block flow
, nghĩa là bản thân nó behave như một block, children của nó sẽ trở lại flow bình thường của documentdisplay: flex
là shorthand củadisplay: block flex
, bản thân nó behave như một block (lí do flex container sẽ tự stretch ra full possible width), và children của nó sẽ trở thành flex items.- Tương tự
display: inline
là shorthand củadisplay: inline flow
,display: inline-block
là shorthand của...display: inline flow-root
wait, wat??!! (tại sao có cái value lạ này thì các bạn tự tìm hiểu thêm hoặc chia sẻ ở lần sau nhé :vayvay:) Lí do chúng ta vẫn còn duy trì single value system là do ở thuở hồng hoang nó đúng là chỉ có 1 value duy nhất. Nên để duy trì backward compatibility promise của the web thì cả 2 system phải cùng tồn tại. Hiểu được điều này sẽ giúp các bạn dễ ghi nhớ và không còn cảm thấy hoang mang về behaviour củadisplay
nữa
Làm thế nào để lazy load img?
What: lazy load nghĩa là mình không load resource nào đó trước khi mình thật sự cần đến chúng.
Why: Page load time tốt hơn, tiết kiệm được bandwidth, trải nghiệm người dùng mượt hơn.
How:
- Legacy: Thay vì set url ở src , chuyển thành attribute data-src . Gán class foo cho các img cần lazy load. On scroll, resize, orientation change, chọn .foo , dựa vào offset hoặc boundingRect để xác định img đang nằm trong viewport -> switch data-src thành src để tiến hành load và remove class.
- Modern: Tương tự như trên, nhưng dùng Intersection Observer API để observe và trigger load (Không support IE)
Pitfalls:
- Layout Reflow: Do img chưa được load có height bằng 0 (và width có thể chưa xác định) nên khi img được load, nó sẽ trigger reflow (page content được sắp xếp lại để nhường chỗ cho img vừa được load). Việc này ngoài gây ra trải nghiệm người dùng kém thì nó còn ảnh hưởng để performance, vì khi trigger reflow thì browser sẽ trigger quá trình calculate layout và rendering pipeline phía sau đó nữa. Solution: Dùng placeholder để thế chỗ cho img chưa được load. Nếu biết dimension, có thể set fixed dimension cho container. Nếu biết được aspect ratio, có thể dùng inline placeholder bằng png hoặc svg, hay thậm chí là CSS. Nhưng khỏe nhất và xịn nhất thì dùng Low-quality image placeholders (LQIPs), cái này nhiều asset source sẽ generate ra sẵn khi bạn upload, còn không có thì hãy đi vận động hành lang để có :vayvay:
- No javascript: Lưu ý cần cung cấp fallback (noscript) trong trường hợp người dùng tắt js hoặc không load đc js vẫn có thể thấy được img. Cái này cũng có issue của riêng nó, nhưng có lẽ sẽ đề cập ở một post nào đó khác.
inputmode attribute của thẻ Input trong HTML
Với các giá trị hợp lý sẽ hiển thị giao diện keyboard phù hợp, giúp cho người dùng có thể nhập data dễ dàng hơn khi sử dụng các thiết bị mobile. Xem browser compatibility trên MDN thì thấy xanh mướt.
Link chi tiết: https://twitter.com/addyosmani/status/1348902534363152385
Làm thế nào để load font một cách hiệu quả nhất?
Làm thế nào để load font một cách hiệu quả nhất? Hai vấn đề thường xuyên gặp phải:
- Render blocking
- Flash Of Unstyled Text (FOUT - render fallback font while loading) và Flash Of Invisible Text (render invisible text while loading) (FOIT)
Context: Render blocking gây ảnh hưởng nặng đến First Meaningful Paint timing. Đặt biệt là nếu load font từ third party source (Vd Google Fonts). Nếu import font url ở trong stylesheet thì browser sẽ phải chờ đến khi nhận được và bắt đầu parse stylesheet mới aware là có cái font resource đó, dẫn đến việc bị block lâu hơn. Để giải quyết vấn đề này, chúng ta thêm 2 bước sau:
- dùng rel=preconnect để establish early connection đến 3rd-party domain đó.
- dùng rel=preload để báo cho browser biết đây là high priority resource và cần tiến hành fetch asap.
Đến lúc này thì tùy vào strategy, các bạn sẽ phải deal với FOUT hoặc FOIT. Nếu performance quan trọng, nên cân nhắc display ngay lập tức và xử lí để quá trình swap diễn ra một cách suông sẻ nhất. Nếu aesthetics quan trọng, có lẽ nên consider FOIT. Tùy case của mỗi người