🌎
web.dev
  • 🌏web.dev
    • 🚧todo
    • 🧪lab
    • 🚸legend
    • 🗒️template
    • 🎹shortcuts
    • 📗reference
      • 📘JS spec
      • 📙javascript.info
      • 📙Eloquent JavaScript
      • 📙JavaScript: The Definitive Guide
      • 📗book
        • 📗You Don't Know JS: series (v.1)
        • 📗You Don't Know JS: series (v.2)
          • 📗YDKJS: Scope & Closures (v.2)
        • 📗Functional-Light JavaScript
      • 🔰website
        • 🔰freeCodeCamp
    • ❤️fav
  • ⚙️tools
    • 🔰tooltip
    • 🔰syntax highlighting
  • 🔰HTML
    • 🔰element
      • 🎛️<select>
      • <form>
      • 🏷️<hr>
      • <figure>
      • <input>
        • type="range"
        • type="radio"
    • block vs. inline
    • Semantic Elements
  • 🔰CSS
    • ⭐box model
      • height
      • border
      • outline
      • 🔸margin
        • layout with margin
        • default margin
        • 🔸negative margin
        • margin collapsing
      • 💊box-sizing
    • 🔰layout
      • 🔹position
        • 🔸positioned ancestor
        • 🔸nearest positioned ancestor
      • 🔹top
      • 🔹display
        • 🔸block
          • inset
        • 🔸inline
        • 🔸flex
          • 🚧Flexbox
            • flex-wrap
            • justify-content
            • align-content
            • flex
            • container properties
          • 🔸flex-direction
          • 🔸align-items
        • 🔸inline-block
        • 🔸inline-flex
        • 🔸none
      • 🔸formatting context
      • Layout System
        • Lorem
        • Spacer
        • VStack
        • HStack
        • Box
        • VCenter/HCenter
        • Dark Theme
      • ✨examples
        • Email Form
      • 🔰CSS grid
      • vertical-align
      • float
      • vertical centering
        • center with "transform"
        • center with "flex"
      • Block Formatting Context
    • 🔰values
      • 🔰units
        • 🔰相對單位
          • 🔸percentage (%)
          • 🔸rem
          • 🔸em
      • 🔰colors
        • HSL
      • 🔰gradients
        • radial-gradient()
    • 🔰variables
    • 🔰functions
      • 🔹calc()
    • 🔰selectors
      • select direct children by JS
      • basic selectors
      • attribute selectors
      • 🔰combinators
      • pseudo-class
      • 🔸pseudo-element
      • custom selectors
    • 🔰properties
      • get/set CSS props
      • Logical Properties
      • overflow
    • 💡Tips
    • CSS import
    • Responsive Design
    • CSS Rules
      • CSS reset
    • Design
      • 🔰CSS shadows
    • ✨examples
      • styling <details>
      • undefined tag
      • modal dialog
      • nav bar
      • glowing buttons
    • Cascade
      • Order
      • Specificity
      • Inheritance
    • Stacking Context
    • Font
      • Font Awesome
      • font-family
    • Text
    • List
  • 🍒JS
    • 💡tips
      • fill 2D array
    • 🧚technique
    • ⭐feature
      • ⭐️ ES5
      • ⭐️ ES6 (2015)
      • ⭐️ ES2017
      • ⭐️ ES2016
      • ⭐️ ES2018
      • ⭐️ ES2020
    • 🔰concept
      • 🔰execution context
        • 📘this
          • ❗"this" determined on call site❗️
        • 🔰running execution context
        • 🔰lexical environment
          • 🔰environment record
          • 🔸[[Environment]]
          • ❗lexical environment optimizaton
        • 🔰call stack
      • 🔰context
        • 🔰global context
        • 🔰class body context
      • 🔰environment
        • ⚙️JS engine
          • 🔰mode
            • 🛡️strict mode
              • 🛡️eval has its own scope in strict mode
              • ✅always use strict mode
            • ❗sloppy mode
              • ❗accidental global variable in sloppy mode❗️
              • ❗modify current scope at runtime❗️
            • ⚖️strict vs. sloppy
          • 🔰v8
        • 🔰Node.js
      • 🔰associated array
      • 🔰parser
    • 🔰compilation
      • 🔰lexing
      • 🔰parsing
      • 🔰code generation
      • 🔰JS is compiled
      • 🔰compile-time
      • 🔰runtime
    • 🔰grammar
      • 🚩directive
        • 📜'use strict'
      • *️token
        • 🗝️keyword
          • 🔐reserved word
            • ❗reserved word as property name is allowed❗️
          • 🔑contextual keyword
        • 🔣punctuator
          • *️hash (#)
          • 🔣semicolon (;)
            • 🔰automatic semicolon insertion
            • 🔰statements that must end with ";"
          • 🔣dot (.)
          • 🔣comma (,)
          • 🔣colon (:)
          • 🔣plus (+)
          • 🔣equal (=)
          • 🔣3 dots (...)
          • 🔣brackets []
          • 🔣braces {}
          • 🔣parentheses ()
          • 🔣question dot (?.)
          • 🔣question (?)
        • 🆔identifier
          • 🆔special identifier
          • ⚖️identifier vs. string
          • ⚖️identifier vs. keyword
          • ❗undeclared identifier
          • 🔰static binding
          • 🔰dynamic binding
        • ✡️literal
          • 🔰trailing comma
      • 🔰comment
      • 🈯declaration
        • ❗redeclaration
        • 🈯lexical declaration
      • 📜statement
        • 📦expression
          • 📦primary expression
          • 📦invocation expression:f()
            • 📦method invocation
          • 📦left-hand side expression
        • 🔁loop
          • 🔁while
          • 🔁do...while
          • 🔁for loops
            • 🔁for
              • 🔰"for-init" scope
              • 🔰"for-init" block
            • 🔁for-in
            • 🔁for-of
              • ❗arrays are iterated "live"❗️
              • 🔰for-of with objects
              • 🔰for-of with strings
              • ⚖️for-of vs. forEach
            • 🔁for-await
            • ⚖️for-in vs. for-of vs. in
            • 📘arr.forEach()
        • 🔀control flow
          • ⤴️jump
            • ⤴️return
            • ⤴️continue
            • ⤴️break
            • ⚖️continue vs. break
          • 🔀branch
            • 🔀if
            • 🔀switch
              • 🔀case clause
        • 📜other statement
          • 📜block
            • 🔰block scope
          • 📜label
          • 🛑with
          • 📜empty statement
        • ⛔statement expected❗️
      • 🚧operator
        • 🚧table of operators
        • 📖terms for operator
          • 🔰arity
          • 🔰associativity
          • 🔰lvalue
          • 🔰dividend
          • 🔰divisor
          • 🔰non-numeric operand
          • 🔰short-circuiting
        • 🔴assignment
          • ⭐destructuring assignment
            • 🔰destructuring array
            • 🔰destructuring iterable
            • 🔰object destructuring
            • 🔰nested destructuring
              • 🌟nested default values
            • 🔰destructuring arguments
          • ➕assignment (=)
        • 🔴arithmetic operator
          • ⭕unary arithmetic operator
            • 🚧unary plus (+)
          • ⭕binary arithmetic operator
            • ➕add/concate (+)
            • ➕division (/)
            • ➕remainder (%)
              • 💾modulo(a, b)
              • 💾mod(a, b)
              • ⚖️remainder vs. modulo vs. mod
            • ➕exponentiation (**)
          • ⭕bitwise operator
            • 🌟2's complement
            • 💾bitview()
            • ➕bitwise not (~)
              • 🔰double tilde (~~)
              • ⚖️(~~) vs. Math.trunc()
        • 🔴relational operator
          • 🔰equality & inequality operator
            • ➕sloppy equality (==)
            • ➕strict equality (===)
          • 🔰comparison operator
          • ➕in
          • ➕instanceof
        • 🚧logical operator
          • ➕nullish coalescing (??)
          • ⭕unary logical operator
          • ⭕binary logical operator
        • ⭕unary operator
          • ➕void
        • ⭕binary operator
          • ➕comma operator (,)
        • ⭕ternary operator
          • ➕conditional operator (?:)
        • 🔰spread operator (...)
        • ➕rest operator (...)
      • 🔰statement vs. declaration
    • 🔰scope
      • 🔰lexical scope
      • 🔰static scope
      • 🔰dynamic scope
      • ⚖️static vs. dynamic scoping
      • 🔰scope chain
      • ⚖️lexical environment vs. scope
      • 🔰top-level scope
      • 🌟global scope
        • 📘global object
          • 🏷️globalThis
          • 🍄window
            • 🔸window.name
            • 🔸window.navigator
          • 🍄global
          • 🔸eval
          • 🔸global object property
        • 💾isCurrentlyInGlobalScope()
        • 🔰global variable
          • 🔰global let
            • ❗global let shadows global object property
          • 🔰global var
        • 🔸DOM element with "id"
      • 🔰module scope
      • 🔰function scope
      • 🔰function name scope
      • 🔰parameter scope
      • 🔰hoisting
        • 🔰function hoisting
          • ❗function hoisting first❗️
        • 🔰variable hoisting
          • 🔰var hoisting
            • ❗function expression not hoisted
          • 🔰let/const/class hoisting
      • ⚖️context vs. scope
    • 💍value
      • 🔰type
        • 💍falsy
        • 📖terms for type
          • 🔰statically typed
          • 🔰dynamically typed
        • 🔰type conversion
        • 🔰type name
          • ➕typeof
          • 💾baseTypeName()
          • 💾typeName()
          • 💾custom type name
        • 🔰nested type
        • 👔type functions
          • 💾functionDefinition()
          • 💾isObject()
          • 💾isPrimitive()
          • 💾isFunction()
          • 💾isClass()
      • 🦠primitive
        • 💍nullish
        • 🔸undefined
        • 🔐null
        • 🔢Number
          • 🔰floating-point
          • 🔰integers
          • 🔴special number
            • 🔢Infinity
            • 🔢NaN
            • 🔢Number.EPSILON
            • 🔢Number.MAX_VALUE
            • 🔢Number.MAX_SAFE_INTEGER
          • 👔Number+ext
            • 💾n.toHex()
            • 💾n.isEqual()
            • 💾isNumber()
          • 👔Random
            • randomInt()
            • randomFloat()
          • 👔Quaternion
        • 🔢BigInt
        • 🦠Boolean
        • 🔤String
          • 🔰raw string
          • 🔰escape sequence
          • template literal
            • tagged template
            • tag function
          • 🔰String methods
            • str.map()
            • 💾str.capitalize()
            • str.countLetters()
            • 💾str.htmlToElement()
            • str.kebabToCamelCase()
            • 📘str.match()
              • ✨split into course names
            • 📘str.matchAll()
              • ✨matchAll() using named groups
              • ✨words in sentence
            • str.random()
            • str.removeWhitespaces()
            • 📘str.replace()
              • 🔰replacer function
              • ✨minus one
            • 💾str.replaceWhitespaces()
            • str.reverse()
            • str.shuffle()
            • 💾str.slice2()
            • 💾str.splice()
            • str.escapeHTML()
            • str.toDatasetPropName()
            • 💾str.words()
            • str.wordCounts()
            • String.fromCharCodes()
          • 👔String extension
            • 💾str.isKeyword
            • 💾str.isReservedWord
            • 💾str.isPunctuator
          • 🔰Unicode
            • 🔰code point
            • 🔰grapheme cluster
            • 🔰code plane
            • 🔰character encoding
              • 🔰UTF-16
                • 🔰code unit
                • 🔰surrogate pair
                • 🔰scalar
        • 🔺Symbol
          • 🔺Symbol.iterator
        • 🔰primitive wrapper
      • 🍄object
        • 🔸property
          • 🚩attribute
            • 🍄property descriptor
            • 💾ownPropertyFlags(obj)
          • 🔸getter/setter
          • 🔸property name
            • ⭐computed property name
          • ⭐shorthand property
          • 🔸internal property
          • 🔰property access
            • 📦property access expression
            • ➕dot notation (.)
              • ⚖️dot notation vs. decimal point
            • ➕bracket notation []
            • ➕optional chaining (?., ?.[])
              • ❓is Optional Chaining Supported?
              • 💾obj.prop(path)
            • ➕optional invocation ?.()
              • is Optional Chaining Supported?
              • obj.prop(path)
            • 🌟chaining rules
              • ✨jane.boyfriend?.money.more
          • 🔰property creation/deletion
            • 📘Object.defineProperty()
            • ➕delete
              • ❗unqualified identifier for delete
          • 🔰property testing
          • 🔰property enumeration
        • 🔰creating objects
          • ✡️object literal
            • ❗object literal is not a block❗️
          • ➕new
            • 🔸new.target
            • ⚖️callable vs. constructable
          • Ⓜ️Object.create()
            • ✨"pure" object
            • ✨guard against accidental modifications
        • 🔰extending objects
          • 📘Object.assign()
            • ⛔️ Object.assign causing TypeError
            • ❗️Object.assign copies with getter/setter
          • inheritance
            • 🔸__proto__
            • class A vs. class A extends Object
          • mixin
            • 💾mergeWithoutOverride()
            • 💾.assignDescriptors()
            • mixin inheritance
            • ✨mixin: HandleEvents
          • 💾 add default properties
          • extending built-in classes
        • 🔰converting objects
          • 🔰serializing objects
          • 🌟object -> primitive conversion
            • 🔘prefer-string
            • 🔘prefer-number
            • 🔘no preference
          • Ⓜ️.toString()
          • Ⓜ️.valueOf()
          • 💾valueToString()
        • Ⓜ️method
          • 💠[[HomeObject]]
          • 🔰polymorphism
        • 🔸prototype
          • 🔰prototype chain
            • 🚧printPrototypeChain()
      • 🍄function
        • 🔰creating functions
        • 🈯function declaration
          • ❗function in block (FiB)
          • ❗function redeclaration
          • 📘function declaration instantiation
        • 🔰function expression
          • ❗read-only identifier
          • 🔰named function expression
          • 🔰as variable
          • 🔰IIFE
        • 🔰arrow function
          • ❗arrow function as method❗️
          • 🔰arrow function as argument
          • ❗arrow function as class field❗️
          • ❗arrow function returning object literal needs (...)❗️
          • 🔰arrow function expression
        • 🔰closure
          • ❗scopes matter with closures
          • 💡private property by closure
          • ✨closure examples
            • ✨closure: to close over or not
            • ✨closure: manage grades
            • ✨closure as object
        • 🌿sorts of function
          • 🔰callback
          • 🔰higher-order function
            • 💾not(f)
            • 💾pipe(f, g, ...)
            • 💾partial(f, a, b ...)
            • 🧚decorator
              • 💾saveCallHistory(f)
              • 💾delay(f, s)
              • 💾benchmark(f)
              • 💾debounce(f, s)
              • 💾throttle(f, s)
              • ⚖️debounce vs. throttle
              • 🧚memoization
                • 🧚memoize by decorator
                  • 💾memoize(f)
                • 🧚memoize by closure
          • 🔰predicate
          • 🔰nested function
          • 🔰recursive function
            • ✨findSolution()
            • 💾deepEqual()
        • 🔰function boundary
        • 🔰function overloading
        • 🔰function forwarding
        • 🔸return value
        • 🔸function name
          • 🔰anonymous function
        • ⚖️parameter vs. argument
        • 🔸argument
          • ❗arguments are passed by value❗️
        • 🔸parameter
          • 🔸default parameter
          • 🔸rest parameters
            • 🔸rest parameters as object
          • 🔸destructured parameter
          • ⚖️simple vs. non-simple parameter
          • ❗deplicate parameters
        • 🔸prototype
        • 🔹constructor
      • 🍄class
        • 🔰initialization
          • 🔹static block
        • 🔸member
          • 🔸class field
          • 🔸private member
          • 🔸static member
            • 🔰init static property
          • private/protected members
          • 🔸getter/setter
          • 🔸method
          • 🔸bound method
        • 🔰creating class
          • 🔰delegation
        • 🔰inheritance
          • 📘super
          • derived class init
          • override constructor
          • override methods
          • override class fields
        • ✨examples
          • ✨Complex
          • ✨TypedMap
          • ✨Bag
      • 📘built-in objects
        • 📘Object
          • 🔸Object.prototype
          • 💾Object extension
            • 💾obj.mergeWith()
        • 📘Function
        • 📘Array
          • 🔸element
            • 🔸element index
              • ❗element index is string❗️
          • 🔸array length
          • 🔸array object property
          • 🍄sparse array
            • 🔰how array methods deal with "holes" ?
          • 🔰array-like
            • 👔Object+arrayLike
          • 🔰creating arrays
            • ✡️array literal
              • 🔰undefined element
          • 🔰accessing elements
          • 🔰iterating elements
          • 📘array methods
            • 📘arr.entries
            • 📘arr.filter()
            • 📘arr.flatMap()
            • 📘arr.reduce()
              • early break reduce()
            • 📘arr.slice()
            • 📘arr.splice()
          • 👔Array extension
            • 💾arr.containsSubarray()
            • 💾arr.copy()
            • 💾arr.isEmpty
            • 💾arr.last
            • 💾arr.none()
            • 💾arr.reversed()
            • 💾arr.shuffle()
            • 💾arr.randomElement
            • 💾arr.removeDuplicates()
            • 💾arr.uniqueMerge()
            • 💾arr.removeUndefined()
            • 💾arr.removeValue()
            • 💾arr.sum(), .average()
            • 💾arr.topN()
            • 💾arr.rank()
            • 💾arr.padEndSpaces()
            • 💾arr.max()
            • 💾arr.isMatrix
            • 💾arr.indexDictionaryForValues()
            • Array extensions (archive)
          • ✨array examples
            • 💾objects to HTML table
          • array & matrix methods
          • static methods
            • 📍Array(n) vs. Array(n).fill()
            • Array.from()
            • 🚧Array.list(n)
          • matrix methods
            • mat.matrixMap()
            • mat.transpose()
            • mat.maxElementInEachColumn()
          • Matrix methods
            • 🌀 Array.matrixFill()
        • 📘Set
          • 👔Set extension
            • 💾set.isEqualTo()
        • 📘Map
          • Don't use object properties
        • 📘RegExp
          • 🚩regex flag
            • 🚩flag /u
            • 🚩flag /d
          • 🔸lastIndex
          • 🔰creating regex
          • 🔰using regex
          • 🔰pattern
            • 🔰special characters
            • 🔰anchor
            • 🔰repeat
              • 🔰greedy vs. lazy
            • 🔰character set
            • 🔰alternation (or)
            • 🔰group
              • 🔰capturing group
                • 🔰named group
            • 🔰lookaround
            • 🔰inline modifier
            • 🔰replace pattern
            • 😀emoji
          • ✨regex examples
            • ✨parse .ini file
            • ✨quick brown fox
            • ✨JS-style numbers
        • 📘Proxy
          • 🔹handler method
          • 📘Reflect
          • ✨Proxy examples
            • ✨identity (proxy)
            • ✨read-only proxy
            • ✨observe()
        • 📘Date
    • 🔰variable
      • 🈯variable declaration
        • 📜var
          • ✅stop using var❗️
          • ❗var is a statement❗️
          • ❗var has no block scope❗️
          • ❗accessing var before declaration gets undefined❗️
          • ❗global var / function is global object property❗️
          • ❗var redeclaration applied even in strict mode❗️
          • ❗var in block can't shadow outer let❗️
          • ❗var can shadow parameter even in strict mode❗️
        • 🈯const
          • ⛔const requires initialization
          • ⛔const can't reassign
          • ⛔no const for classic "for"
        • 🈯let
          • ✅let redeclaration not allowed even in sloppy mode❗️
          • ✅let can't shadow parameter even in sloppy mode❗️
        • 🔰initial value
      • ❗variable redeclaration
        • ❗var redeclaration
        • ❗let redeclaration
      • 🔴accessing variables
        • ⛔temporal dead zone
          • ⛔TDZ
          • ⛔typeof let/const/class in TDZ gets an error❗️
        • ⛔uninitialized variable
        • 🔰LHS reference
        • 🔰RHS reference
        • ⚖️LHS vs. RHS reference
      • ❗variable shadowing
      • ❗can't delete variable/function❗️
    • 🔰module
      • ⭐ES module
        • 🈯import
          • ✨import ES module
          • dynamic import()
        • 🈯export
          • 📘default export
          • 📘named export
      • 🔰CommonJS
        • 📜package.json
      • ⚖️ES vs CommonJS
      • 🔰import.meta
      • 🔰package
      • 🔰bundler
      • 🔰minifier
      • 🔰browser-specific features
      • 🔰module pattern
    • 🔰iteration
      • 👔Iterable+ext
        • 💾range()
        • 💾obj.isIterable
      • 🔰iterable
        • 🔸make-iterator method
        • ❗only-iterate-once iterable
        • 🔰make iterables
        • ✨iterable examples
          • ✨ClosedRange
          • ✨Sequence
      • 🔰iterator
        • 🔸next()
        • 🔸return()
        • ❗iterators only iterate once❗️
        • 🔰iterable iterator
        • 🔰infinite iterator
        • 🔰make iterator iterable
        • 💾IteratorPrototype
        • 💾Iterator
      • 🔰iteration result
      • 📘Generator
        • 🔰generator function
          • 📘function*
          • 📦yield
          • 📘yield*
          • 🔰composition
          • 🔰generator function as ...
        • ✨generator examples
          • 💾*list()
          • 💾*integers()
          • 💾*closedRange()
          • 💾*fibonacci()
          • 💾*zip()
          • 💾*sequence()
          • 💾*interleave()
    • 🔰async code
      • 🔰thread
      • 🔰event loop
      • 🍄Promise
        • 👔custom promises
          • 💾getJSON()
          • 💾Promise.wait()
          • 💾futureValue()
        • 🔰using Promises
        • 🔰error handling
          • ⭐finally()
          • 🔰recoverable errors
        • 🔰chaining Promises
          • 💾Promise.inSeries()
        • 🔰Promises in parallel
      • 🆔async
        • 🔰async function
        • 🔰async method
        • 🔰async arrow function
        • 🔰async IIFE
        • 💾measureTime()
      • ➕await
        • 🔰await promises
          • 🔰await sequentially
          • 🔰await in parallel
        • 🔰await "thenable"
        • 🔰error handling
    • 🔰debugging
      • 🔰testing
      • 💊error handling
        • ⤴️throw
        • 🔰rethrow
        • ⤴️try-catch-finally
      • 🛠️devtools
        • 📜debugger
        • 🔰show built-in shadow DOM
    • ⛔Error
      • 🔴compile-time error
        • ⛔️ early errors
      • 🔴runtime error
      • ⛔️ SyntaxError
        • ⛔duplicate parameter not allowed in strict mode❗️
        • ⛔unexpected token "xxx"❗️
        • ⛔Named export 'xxx' not found❗️
        • ⛔unexpected number❗️
        • ⛔... parenthesis must be used to disambiguate operator precedence❗️
        • ⛔identifier 'xxx' has already been declared❗️
        • ⛔missing initializer in const declaration❗️
        • ⛔lexical declaration cannot appear in a single-statement context❗️
        • ⛔delete of an unqualified identifier in strict mode❗️
        • ⛔octal literals not allowed in strict mode❗️
      • ⛔️ ReferenceError
        • ⛔cannot access 'xxx' before initialization❗️
        • ⛔'xxx' is not defined❗️
      • ⛔️ TypeError
        • ⛔'xxx' is not a function❗️
        • ⛔assignment to constant variable❗️
        • ⛔cannot read properties of nullish❗️
        • ⛔cannot convert BigInt to Number❗️
        • ⛔cannot convert Symbol to Number❗️
        • ⛔cannot assign to read only property 'prototype' of function 'xxx'❗️
    • 🏛️Libraries
      • 🏛️JSXGraph
      • 🏛️grapheme-splitter
    • 🛠️tools
      • transpilers
    • ✨examples
      • Code Wars
        • Next Bigger Number
        • Changing Money
        • Game of Life
      • Exercises
        • flattenObject()
        • Pascal's Triangle
        • Permuations
        • Spiral Matrix
    • 💼projects
      • 💼Dictionary App
      • 💼Language: Egg
      • Calculator ❤️
      • Synth Keyboard ❤️
      • Form Validation
      • Password Generator
  • 🔰web component
    • 🔸custom element
      • lifecycle methods
      • getter/setter for attributes
      • Element Types
      • Element Info
      • Observed Attributes
      • Element Upgrades
      • Unknown Elements vs Undefined Custom Elements
      • Element-defined Content
    • 🔸shadow DOM
      • light, shadow, flattened DOM
      • shadow root vs. host
      • shadow DOM styles
        • Use CSS Variables
        • CSS reset in Shadow DOM
      • shadow DOM events
        • retargeting
        • Active Element
        • Events that Cross Shadow DOM Boundary
        • Bubbling in Shadow DOM
      • shadow DOM slots
    • 🔸<template>
      • 🔸<slot>
        • 🔸named slot
        • 🔸fallback content
        • 🔰slotted node styles
        • 🔸slotted nodes
        • 🔹`slotchange` event
        • 🔰Slot API
    • 🔸light DOM
    • 🔰implementing components
    • 🔰using components
      • 💡hide components until defined
    • Built-in Components
      • <details>
    • ✨web component examples
      • ✨<search-box>
      • <my-log> ❤️
      • <element-details>
      • <custom-dialog>
      • <user-card>
      • <expanding-list>
      • <dom-hierarchy> ⭐️
      • <time-formatted>
      • <live-timer>
      • <error-message>
      • <popup-info>
      • <custom-menu>
  • 🌐browser
    • 📘web API
      • 📘clearTimeout()
      • 📘setTimeout()
      • 🍄URL
      • 🍄navigator
      • 🔰web worker
        • 🍄self
    • 🔰concepts
      • 🔰coordinates
    • 🔰Event
      • 🔸event.type
      • 🔸event.currentTarget
      • 🔸event.target
        • ✨buttons A, B, C
      • 🔰event dispatching
      • 🔰event propagation
        • 🔰bubbling phase
        • 🔰capturing phase
        • 🔰stop propagation
          • ✨button in clickable paragraph
      • 🔰event handler
        • invocation context
        • ❌return value
        • 🔰"this" in event handler
        • 🔰register handler
          • 🔰handler options
          • capturing handler
          • 🍄object handler
        • 🔰remove handler
        • ❗window-reflecting body element event handlers
      • 🔰default action
        • ✨hide or not to hide?
        • ✨link going nowhere
      • 🌿event types
        • 🔰key event
          • ✨press shift + space
        • 🔰input event
        • 🔰mouse event
          • 🔸.buttons
          • 🔸.button
          • 🔰"click"
            • ✨draw dots
            • ✨sliding menu
            • ✨closing button [x]
            • ✨click to slide (carousel)
            • ✨click to move ball
          • 🔰"dblclick"
          • 🔰"mousemove"
            • ✨drag the bar
          • 🔰drag event
          • 👔MouseEvent+ext
          • ✨mouse event examples
            • ✨mouse event coords
        • 🔰touch event
          • 🔸.touches
          • 🔸.targetTouches
          • 👔TouchList+ext
        • 🔰scroll event
          • ✨scroll me
          • ✨scroll progress
          • ✨scroll progress on body
        • state-change
        • CSS events
      • client-side JavaScript timeline
      • custom events
        • event.isTrusted
      • "change" event
    • 🔰DOM
      • 🔰DOM hierarchy
      • 🔰querying elements
        • 💾$(), $all()
      • 🔰traversing DOM
      • 🔰create/insert/delete nodes
        • ✨table of contents
      • 🌿DOM types
        • 📘Node
          • 🔰.tagName vs .nodeName
          • 👔Node+ext
            • 💾node.isInPage
            • 🚧node.traverse()
            • 💾node.$(), .$all()
            • 🚧node.appendTag()
            • 💾newElement()
        • 📘Element
          • 🔸element content
          • 🔹.insertAdjacentHTML()
          • 🔸attribute
            • 🔸id
            • 🔰data attributes
            • ⚖️attributes vs. properties
          • 🔰box models
          • 👔Element+boxes
            • 🔸.scrollBox
              • 🔰scrollbar width
            • 🔸.paddingBox
            • 🔸.borderBox
              • 🔸.offsetParent
            • 🔸.boundingBox
              • ✨elem.showNote()
          • 👔Element+ext
            • 💾elem.isInside()
            • 💾elem.isHeading
            • 💾elem.wrappedWith()
            • 💾elem.wrappedWithHTML()
            • 🚧elem.attr()
            • 🚧elem.styleProp()
            • 🚧elem.showDataAttr()
            • 💾elem.position()
        • 🍄HTMLElement
          • 💾htmlElem.isHidden
        • 📘Document
          • 🔸document size
          • 🔸methods
            • 📘doc.elementFromPoint()
        • 📘Window
          • 🔹.requestAnimationFrame()
          • 🔰viewport
          • 🔰window scrolling
            • 🔰scroll smoothly
            • 🔰scroll lock
        • 🍄DocumentFragment
        • 🍄NodeList
          • NodeList vs. Array
        • CSSStyleDeclaration
      • ✨DOM examples
      • 👔custom methods
        • 🚧prop()
        • 🚧tag()
    • 🔰SVG
      • 💾SVGElement()
      • Attributes
        • fill
        • stroke-dasharray
        • transform
      • 🔰gradients
        • 📘<linearGradient>
        • 📘<radialGradient>
      • Patterns
      • 🔰SVG shapes
        • <rect>
        • <line>
        • <polygon>
        • <polyline>
        • <ellipse>
        • <path>
      • ✨SVG examples
        • SVG as background image
        • 3 circles
        • ✨SVG Clock
        • ✨pie chart
      • namespace
      • tools
    • 🔳<canvas>
      • 👔Canvas+ext
        • 💾drawOnCanvas2D()
        • 💾ctx.point()
        • 💾ctx.polyline()
        • 💾ctx.roundedRect()
      • 🔰rectangle
      • 🔰path
        • 🔰nonzero winding rule
        • 📘arcTo()
        • ✨curves
      • 🔰text
      • 🔰image
        • ✨canvas snapshot
      • 🔰graphics state
        • 🔰fill / stroke styles
          • 🔰colors
            • ✨color wheel
          • 🔰patterns
          • 🔰gradients
            • 💾ctx.gradient()
        • 🔰shadows
        • 🔰compositing
        • 🔰transformation
          • ✨Koch snowflake
        • 🔰clipping
      • ✨canvas examples
        • ✨square & circle
        • ✨open/closed subpaths
        • ✨fractal
        • ✨regular polygons
    • 🔰storage
    • 🔰animation
      • ✨animation examples
        • ✨moving cat
        • ✨cat in hat
        • ✨cat behind hat
  • 🔰React
    • 啟動
    • Hello React❗️
    • React Projects
      • FullStackOpen
        • Part 1
  • Server
    • HTTP status code
  • 🔖附錄
    • 👔custom
      • 👔element
        • 👔tabs control
        • 👔left tabs
      • 👔custom css
        • 👔Every Layout
      • 👔custom classes
        • 👔Vector
        • 👔Rect
        • 👔Size
        • 💾TableMaker
        • List
        • Point
        • Turtle
        • Matrix
      • 👔custom functions
        • randomElement()
        • randomColor()
        • randomPassword()
        • clone(obj)
        • functionName()
      • helper functions
    • 🧩three.js
      • 👔Director
      • 🔰renderer
        • 🔹set canvas size
      • ✨examples
        • ✨startup
      • 🔰primitives
      • 🔰scene graph
    • 🅰️Google Apps Script
      • 🍄app
        • 🔹helpers
        • 🔸app.init()
        • 🔸members
          • 🔸app.color
          • 💾app.dataRangeValuesFromSheet()
          • 💾app.sheetByName()
          • 💾app.valueOfNamedRange()
          • 💾app.deleteSheetByName()
          • 🚧app.cellValueAt()
          • ⭐app.makeTable()
          • 🚧app.mergedCellValue()
          • 🚧app.parseFields()
          • 🚧app.fetch()
          • 🚧app.resizeColumns()
          • 🚧app.setBorder()
          • 🚧app.writeAwardList()
        • 🔳prototypes
          • 🔳app.sheet.prototype
            • 💾 sheet.delete()
            • 💾 sheet.rangeByRect()
            • 💾 sheet.setValues()
            • 💾sheet.appendConditionalFormatRule()
            • 🚧SheetMethods
              • sheet.values()
          • 🔳app.range.prototype
            • range.alignCenter()
            • range.cellValue()
            • range.contains()
            • 🚧range.isEqualTo()
        • 🟥app menu
      • 🍄RawData
      • 🌿Classes
        • 🍄Sheet
        • 🍄Range
          • 🔰A1 notation
            • 💾columnName()
            • 💾A1Notation()
            • 💾A1NotationToRowColumn()
          • 🔰border
          • 📘range.setNumberFormat()
          • range.setRichTextValue()
        • 🍄ConditionalFormatRule
        • 🍄RichTextValueBuilder
        • 🍄Protection
        • 🍄Ui
      • 👔custom objects
        • 👔RangeRect
          • 💾rect.cell(i, j)
          • 💾RangeRect.byData()
        • 🚧BorderStyle
        • 🚧SheetFields, SheetField
      • 💼projects
        • 💼不能補考名單
        • 💼各班平均及前三名
        • 💼國中成績一覽表
        • 💼高中獎學金名冊
      • 💡tips
        • 💡 custom prototypes
        • 💡 refresh cells
        • 💡 條件格式:自訂公式
        • 處理字串
        • 顯示中文星期幾
        • 在儲存格使用陣列
        • Count non-blank cells
        • 💡 快速調整欄位寬度
      • ❓questions
      • 📊Google Sheet
      • 🔸GAS ⟩ commands
      • ✨GAS ⟩ examples
        • examples
        • ✨split data into tabs
          • migrate sheets into files
        • ✨event: onEdit
      • 🔰GAS ⟩ Events
        • onEdit
      • Dialogs & Sidebars
    • 📖JSDoc
      • return value
      • destructured parameter
    • 📦data structure
      • 📦Queue
      • 📦Stack
      • 📦WaitingList
      • 📦Graph
        • 🔹.findPath()
        • 🔹.findShortestPath()
        • 🤖breadth-first search
        • 🤖depth-first search
        • ✨Graph examples
          • ✨Mail Robot
    • 🔰algorithm
      • 🔰recursion
        • ✨recursion count
      • 🔰dynamic programming
      • Sort
        • sort strings
        • sort by multiple keys
        • bubble sort
      • ✨problems
        • ✨find couple
        • ✨knapsack problem
        • ✨longest path
    • 🔰paradigm
      • 🔰Test Driven Development (TDD)
      • 🔰Functional Programming (FP)
    • 🔰TypeScript
      • Setup
      • ⭐️ Cheat sheet
      • Type
        • type annotation
        • type predicate
        • Primitives
        • Narrowing
        • Tuples
        • Generics
          • Array<T>
          • Generic Functions
        • Object
          • Object Types
          • Object Properties
        • Function
          • optional/default parameters
          • return type
          • function type expression
          • callable objects with properties
          • function overloads
        • Union
          • discriminated union
      • TS Operators
    • 💎resource
      • ✏️editor
        • ✏️VSCode
          • emmet
          • shortcuts
        • ✏️replit.com
          • 🔰import .js file
      • 🔧tools
        • Vectornator
          • Tutorials
      • 📚books
Powered by GitBook
On this page
  • 心得
  • Code
  • v.1:Table layout (original)
  • v.2:Grid layout
  • v.3:Web Componet

Was this helpful?

  1. 🍒JS
  2. 💼projects

Calculator ❤️

PreviousLanguage: EggNextSynth Keyboard ❤️

Last updated 3 years ago

Was this helpful?

🔸 心得┊ 1. Table layout ┊ 2. Grid layout ┊ 3. Web Component ❤️

  • MakeUseOf ⟩ How to Build a Simple Calculator Using HTML, CSS, and JavaScript

  • CSSTricks ⟩

    • Fitting Text to a Container

    • Viewport Sized Typography

    • A Complete Guide to Grid

  • NewbeDev ⟩ How to make font-size relative to parent div? - use React.

  • MDN ⟩

    • <input> - input types

    • <template> : HTMLTemplateElement

    • Error - error.message

  • Grid

  • Web Components

  • Google Fonts ⟩ Orbitron

  • Why isn't my button function clear() working?

  • Is "clear" a reserved word in Javascript?

  • Font scaling based on width of container

心得

<!-- 📁 index.html -->
<script type="module" src="calc-app/calc-app.js"></script>

由 module 透過 customElements.define( ) 讀進來的 class CalcApp,可以直接用嗎❓例如:const app = new CalcApp( );

// ⭐️ 1. export class (📁 calc-app.js)
export default class CalcApp extends HTMLElement { ... }

// ⭐️ 2. import class (📁 main.js)
import CalcApp from './calc-app/calc-app.js';

如果我們有:

<template>
  <div></div>
</template>

這時注意❗️ template.querySelector('div') === null ❗️ 💾 codepen

const {log} = console;

const template = $('template');
const div1 = $('div', template);
const div2 = $('div', template.content);

log(`template: ${template}`);     // HTMLTemplateElement
log(`div1: ${div1}`);             // ⭐️ null ❗️
log(`div2: ${div2}`);             // HTMLDivElement

// ⭐️ $(): select first element
function $(selector, parent = document){
  return parent.querySelector(selector);
}

HTMLTemplateElement has a content property, which is a read-only DocumentFragment containing the DOM subtree which the template represents. 📘MDN ⟩ <template> ⟩ Attributes

換句話說:template.content 才有 DOM subtree,template 本身並沒有❗️

練習時遇到的怪事:

<input onclick="clear()">

onclick="..." 裡面竟然不能寫 "clear( )"❗️就算寫了,也沒用❗️ 👉🏻 參看上方「🗣 討論」頁。

  • ⭐️ 解釋: 自己寫的 clear() 是 global function,事實上放在 window.clear(),所以當 "click" event 發生時,透過 event bubbling 的機制,document.clear() 會先收到 "click" event,所以就把 window.clear() 也屏蔽掉了。

Document.clear() does nothing, but doesn't raise any error. 📘 Document.clear() - Deprecated❗️

Code

v.1:Table layout (original)

// State
const State = {
    good: 0,
    bad: 1,
    error: 2,
};

// Calculator
class Calculator {
    
    // init
    constructor(){
        this._memory = '';
        this._lastInput = undefined;
        this._debugMode = true;
    }

    // enter key into memory
    input(key){

        if (key == "×") key = '*';
        if (key == "+") key = '+';
        if (key == "-") key = '-';
        if (key == "÷") key = '/';

        // if (this.debugMode) console.log(`key: ${key}`);

        switch(key){

            // clear
            case 'C': 
                this._memory = ''; 
                break;

            // execute
            case '=':
                this._memory = this.isCurrentResultValid
                    ? `${this._currentResult}`
                    : '💥 Error';
                break;

            // in the middle of entering
            default:
                this._memory += key;
                if (this.state == State.error) this._memory = key;
        }
        
        this._lastInput = key;
        this._calcCurrentResult();

        if (this.debugMode) this.log();
    }

    get memory(){
        return this._memory;
    }

    get debugMode(){
        return this._debugMode;
    }

    set debugMode(bool){
        this._debugMode = bool;
    }

    get isCurrentResultValid(){
        return Number.isFinite(this._currentResult);
    }

    get currentResult(){
        return this._currentResult;
    }

    log(){
        console.log(`memory: "${this.memory}", state: ${this.state}, result: ${this.currentResult}`);
    }

    // calculate current result based on memory
    _calcCurrentResult(){

        // empty memory
        if(this._memory == ''){
            this._currentResult = 0;
            return;
        }

        // memory not empty, try to calculate current result
        try {
            // if success, return the result
            const result = eval(this._memory);
            this._currentResult = result;
        } catch(e) {
            // else set current result `undefined`
            if (this.debugMode) console.log(`${e.name}: ${e.message}`);
            this._currentResult = undefined;
        }
    }

    get state(){
        if (this.isCurrentResultValid) return State.good;
        if (this._lastInput == '=') return State.error;
        return State.bad;
    }

}
const { log } = console;

const display = $('#display');
const calcUI = $('.calculator');

const calculator = new Calculator();
// calculator.debugMode = false;

// event listener ---------------------
calcUI.addEventListener('click', e => {

    // make sure target is <input>
    let t = e.target;
    if (t.nodeName !== 'INPUT') return;

    // entering `key` into calculator
    let key = t.value;
    calculator.input(key);
    display.value = calculator.memory;

    // update UI state
    let state = calculator.state;
    display.classList.toggle('bad', state === State.bad);
    display.classList.toggle('error', state === State.error);
});

// helpers ------------------------------

// ⭐️ $(): select first element
function $(selector, parent = document){
  return parent.querySelector(selector);
}
/* Google Fonts: Orbitron */
@import url('https://fonts.googleapis.com/css2?family=Orbitron&display=swap');

/* css reset */

* {
  box-sizing: border-box;
}

.calculator {
  width: 400px;
  height: 380px;
  padding: 10px;
  border-radius: 1em;
  margin: auto;

  background: #191b28;
  box-shadow: rgba(0, 0, 0, 0.19) 0 10px 20px, rgba(0, 0, 0, 0.23) 0 6px 6px;
}

/* highlight cells */
/* tr {
  background: hsl(60, 85%, 70%);
} */

.display-box,
.button {
  font-family: 'Orbitron', sans-serif;
  font-size: 2em;

  width: 100%;
  height: 100%;
  border: 0.5px solid black;
  border-radius: 5px;
}

.display-box {
  background: #dcdbe1;
  color: black;
  text-align: right;
  padding: 0 .5em;
}

.display-box.bad {
    background: hsl(45, 80%, 80%);
}

.display-box.error {
    background: hsl(0, 80%, 50%);
    color: yellow;
}

.button {
  background: hsl(270, 80%, 40%);
  color: white;
}

.button:active {
  background: hsl(270, 80%, 50%);
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.9) inset;
}
<table class="calculator">
  
  <tr>
    <td colspan="3">
      <input class="display-box" type="text" id="display" value="" disabled />
    </td>

    <td>
      <input class="button" type="button" value="C" style="background-color: #fb0066;" />
    </td>
  </tr>

  <tr>
    <td>
      <input class="button" type="button" value="1" />
    </td>
    <td>
      <input class="button" type="button" value="2" />
    </td>
    <td>
      <input class="button" type="button" value="3" />
    </td>
    <td>
      <input class="button" type="button" value="+" />
    </td>
  </tr>

  <tr>
    <td>
      <input class="button" type="button" value="4" />
    </td>
    <td>
      <input class="button" type="button" value="5" />
    </td>
    <td>
      <input class="button" type="button" value="6" />
    </td>
    <td>
      <input class="button" type="button" value="-" />
    </td>
  </tr>

  <tr>
    <td>
      <input class="button" type="button" value="7" />
    </td>
    <td>
      <input class="button" type="button" value="8" />
    </td>
    <td>
      <input class="button" type="button" value="9" />
    </td>
    <td>
      <input class="button" type="button" value="×" />
    </td>
  </tr>

  <tr>
    <td>
      <input class="button" type="button" value="." />
    </td>
    <td>
      <input class="button" type="button" value="0" />
    </td>

    <td>
      <input class="button" type="button" value="=" style="background-color: #fb0066;" />
    </td>
    <td>
      <input class="button" type="button" value="÷" />
    </td>
  </tr>
</table>
  • replit ⟩ calculator v.2

  • codepen ⟩ calculator

v.2:Grid layout

// Calculator State
const State = {
    good : 0,
    bad  : 1,
    error: 2,
};

// Model: Calculator
class Calculator {
    
    // init
    constructor(){
        this._memory = '';
        this._lastInput = undefined;
        this._debugMode = true;
    }

    // enter `key` into memory
    input(key){

        switch(key){
            case "×": key = '*'; break;
            case "+": key = '+'; break;
            case "−": key = '-'; break;
            case "÷": key = '/'; break;
        }

        switch(key){

            // clear
            case 'C': 
                this._memory = ''; 
                break;

            // execute
            case '=':
                this._memory = this.isCurrentResultValid
                    ? `${this._currentResult}`
                    : '💥 Error';
                break;

            // in the middle of entering
            default:
                // append `key` to memory
                this._memory += key;

                // ⭐️ if in error state, clear memory and start over.
                if (this.state == State.error) {
                    this._memory = key;
                }
        }
        
        this._lastInput = key;
        this._calcCurrentResult();

        if (this.debugMode) this.log();
    }

    // this.memory
    get memory(){
        return this._memory;
    }

    // this.debugMode
    get debugMode(){
        return this._debugMode;
    }

    // this.debugMode = true | false
    set debugMode(bool){
        this._debugMode = bool;
    }

    // this.isCurrentResultValid
    get isCurrentResultValid(){
        return Number.isFinite(this._currentResult);
    }

    // this.currentResult
    get currentResult(){
        return this._currentResult;
    }

    // this.log()
    log(){
        console.log(`memory: "${this.memory}", state: ${this.state}, result: ${this.currentResult}`);
    }

    // calculate current result based on `memory`
    _calcCurrentResult(){

        // empty memory
        if (this._memory == '') {
            this._currentResult = 0;
            return;
        }

        // memory not empty, try to calculate current result
        try {
            // if success, return the result
            const result = eval(this._memory);
            this._currentResult = result;
        } catch(e) {
            // else set current result `undefined`
            if (this.debugMode) console.log(`${e.name}: ${e.message}`);
            this._currentResult = undefined;
        }
    }

    // this.state
    get state(){
        // result is OK
        if (this.isCurrentResultValid) return State.good;
        // hit '=' and result is Not OK
        if (this._lastInput == '=') return State.error;
        // still in the middle of entering something
        return State.bad;
    }

    // this.inGoodState
    get inGoodState() {
        return this.state === State.good;
    }

    // this.inBadState
    get inBadState() {
        return this.state === State.bad;
    }

    // this.inErrorState
    get inErrorState() {
        return this.state === State.error;
    }

}
const { log } = console;

const buttonLabels = [
    'C', '1', '2', '3', '+',
    '4', '5', '6', '−',
    '7', '8', '9', '×',
    '.', '0', '=', '÷',
];

// views
const display = $('.display-box');
const calcUI = $('.calculator');

// for each button label, create new button
buttonLabels.forEach(lbl => {

    // append <button> to calculator
    let btn = tag('button', {
        textContent: lbl,
        parentNode: calcUI
    });

    // highlight "command" buttons
    if (lbl === 'C' || lbl === '=') {
        btn.classList.add('command');
    }
    
});

// model
const calculator = new Calculator();
// calculator.debugMode = false;

// event listener ---------------------

calcUI.addEventListener('click', e => {

    // make sure target is <button>
    let t = e.target;
    if (t.nodeName !== 'BUTTON') return;

    // get `key` from UI
    let key = t.textContent;

    // update model
    calculator.input(key);
    
    // update UI
    display.value = calculator.memory;
    display.classList.toggle('bad', calculator.inBadState);
    display.classList.toggle('error', calculator.inErrorState);
});

// helpers ------------------------------

// ⭐️ $(): select first element
function $(selector, parent = document){
  return parent.querySelector(selector);
}

// ⭐️ tag()
function tag(name, {
    textContent,
    attributes = {},
    parentNode,
}={}){

    // create element
    let elem = document.createElement(name);

    // set text content if necessary
    if (textContent) {
        elem.textContent = textContent;
    }
    
    // set attributes
    for (const [key, value] of Object.entries(attributes)) {
        elem.setAttribute(key, value);
    }

    // append to parent node if present
    if(parentNode){
        parentNode.appendChild(elem);
    }
    
    // return element
    return elem;
}
/* Google Fonts: Orbitron */
@import url('https://fonts.googleapis.com/css2?family=Orbitron&display=swap');

/* css reset */

* {
  box-sizing: border-box;
}

/* calculator */

.calculator {

    /* ⭐️ configurable css vars */
    --calculator-user-width: 80vw;
    --calculator-user-height: 80vh;
    --calculator-padding: 10px;

    /* ⭐️ computed css vars */
    --calculator-width: max(var(--calculator-user-width), 115px);
    --calculator-height: max(var(--calculator-user-height), 120px);
    --calculator-cell-min-side: min(
        calc((var(--calculator-width)  - 2 * var(--calculator-padding))/4), 
        calc((var(--calculator-height) - 2 * var(--calculator-padding))/5)
    );

  width: var(--calculator-width);
  height: var(--calculator-height);

  padding: var(--calculator-padding);
  border-radius: 10px;
  margin: auto;

  background: #191b28;
  box-shadow: rgba(0, 0, 0, 0.19) 0 10px 20px, rgba(0, 0, 0, 0.23) 0 6px 6px;
}

.display-box,
button {

  font-family: 'Orbitron', sans-serif;
  font-size: calc(var(--calculator-cell-min-side) * 0.4);
  font-weight: bold;
  /* font-stretch: ultra-expanded; */

  width: 100%;
  height: 100%;
  border: 0.5px solid black;
  border-radius: 5px;
}

.display-box {
  background: #dcdbe1;
  color: black;
  text-align: right;
  padding: 0 .5em;
}

.display-box.bad {
    background: hsl(45, 80%, 80%);
}

.display-box.error {
    background: hsl(0, 80%, 50%);
    color: yellow;
}

button {
  background: hsl(270, 80%, 40%);
  color: white;
}

button.command {
    background-color: #fb0066;
}

button:active {
  background: hsl(270, 80%, 50%);
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.9) inset;
}

/* Grid layout */

.calculator {
    display: grid;
    /* rows / cols */
    grid-template: repeat(5, 1fr) / repeat(3, auto) 1fr;
}

.calculator .display-box {
    /*  r1 /  c1 /   r2   /  c2   */
    /* row / col / height / width */
    grid-area: 1 / 1 / 1 / span 3;
}
<div class="calculator">
  <!-- display-box -->
  <input class="display-box" type="text" value="" disabled />
  <!-- ⭐️ other buttons added by script -->
</div>

v.3:Web Componet

<div class="container centered-element">
  <!-- web component (custom element) -->
  <calc-app width="200px" height="240px"></calc-app>
  <calc-app></calc-app>
</div>

<!-- scripts -->
<script type="module" src="calc-app/calc-app.js"></script>
<script type="module" src="main.js"></script>
import Calculator from './Calculator.js';

const { log } = console;

// ⭐️ 1. custom web component
export default class CalcApp extends HTMLElement {

    // constructor
    constructor(width, height, debugMode=true) {

        // tell HTMLElement to initialize itself.
        super();

        // ⭐️ get `width`, `height` 
        //   from elem attributes or function arguments
        const w = this.getAttribute('width') || width || '120px';
        const h = this.getAttribute('height') || height || '150px';

        // ⭐️ set user's preferred size
        this.style.setProperty('--calculator-user-width', w);
        this.style.setProperty('--calculator-user-height', h);

        // model
        this.calculator = new Calculator();
        this.calculator.debugMode = debugMode;
    }

    // connected to document
    connectedCallback() {

        // attach shadow root
        let root = this.attachShadow({mode: 'open'});

        // ⭐️ clone nodes from <template>
        let template = CalcApp.template;
        let clone = CalcApp.template.content.cloneNode(true);
        root.append( clone );

        // connect views
        this.display = $('.display-box', root);
        this.UI = $('.calculator', root);

        // add event listener ---------------------
        this.UI.addEventListener('click', e => {

            // make sure target is <button>
            let t = e.target;
            if (t.nodeName !== 'BUTTON') return;

            // get `key` from UI
            let key = t.textContent;

            // update model
            this.calculator.input(key);
            
            // update UI
            this.display.value = this.calculator.memory;
            this.display.classList.toggle('bad', this.calculator.inBadState);
            this.display.classList.toggle('error', this.calculator.inErrorState);
        });
    }

    // template HTML
    static get template() {
        if (!CalcApp._template) {

            // create <template> element
            const template = document.createElement('template');
            
            template.innerHTML = ` 
                ${CalcApp.styles}

                <div class="calculator">
                    <input class="display-box" type="text" value="" disabled />
                </div>
            `;

            // append buttons to template
            const buttonLabels = [
                'C', '1', '2', '3', '+',
                '4', '5', '6', '−',
                '7', '8', '9', '×',
                '.', '0', '=', '÷',
            ];

            const calcUI = $('.calculator', template.content);

            // for each button label
            buttonLabels.forEach(lbl => {

                // append <button> to calculator
                let btn = tag('button', {
                    textContent: lbl,
                    parentNode: calcUI
                });

                // highlight "command" buttons
                if (lbl === 'C' || lbl === '=') {
                    btn.classList.add('command');
                }
                
            });

            // save template in a static private var
            CalcApp._template = template;
        }
        
        return CalcApp._template;
    }

    // template styles
    static get styles(){
        return `<style>
        /* Google Fonts: Orbitron */
        @import url('https://fonts.googleapis.com/css2?family=Orbitron&display=swap');

        /* css reset */

        .calculator {
        box-sizing: border-box;
        }

        .calculator * {
            box-sizing: inherit;
        }

        /* calculator */

        .calculator {

            /* ⭐️ configurable css vars */
            --calculator-padding: 10px;

            /* ⭐️ computed css vars */
            --calculator-width: max(var(--calculator-user-width), 115px);
            --calculator-height: max(var(--calculator-user-height), 120px);
            --calculator-cell-min-side: min(
                calc((var(--calculator-width)  - 2 * var(--calculator-padding))/4), 
                calc((var(--calculator-height) - 2 * var(--calculator-padding))/5)
            );

            width: var(--calculator-width);
            height: var(--calculator-height);

            padding: var(--calculator-padding);
            border-radius: 10px;
            margin: auto;

            background: #191b28;
            box-shadow: rgba(0, 0, 0, 0.19) 0 10px 20px, rgba(0, 0, 0, 0.23) 0 6px 6px;
        }

        .display-box,
        button {

        font-family: 'Orbitron', sans-serif;
        font-size: calc(var(--calculator-cell-min-side) * 0.4);
        font-weight: bold;
        /* font-stretch: ultra-expanded; */

        width: 100%;
        height: 100%;
        border: 0.5px solid black;
        border-radius: 5px;
        }

        .display-box {
        background: #dcdbe1;
        color: black;
        text-align: right;
        padding: 0 .5em;
        }

        .display-box.bad {
            background: hsl(45, 80%, 80%);
        }

        .display-box.error {
            background: hsl(0, 80%, 50%);
            color: yellow;
        }

        button {
        background: hsl(270, 80%, 40%);
        color: white;
        }

        button.command {
            background-color: #fb0066;
        }

        button:active {
        background: hsl(270, 80%, 50%);
        box-shadow: 0 0 5px rgba(0, 0, 0, 0.9) inset;
        }

        /* Grid layout */

        .calculator {
            display: grid;
            /* rows / cols */
            grid-template: repeat(5, 1fr) / repeat(3, auto) 1fr;
        }

        .calculator .display-box {
            /*  r1 /  c1 /   r2   /  c2   */
            /* row / col / height / width */
            grid-area: 1 / 1 / 1 / span 3;
        }
        </style>`;
    }
} 

// ⭐️ 2. custom tag <calc-app>
// ⭐️⭐️ custom element names must contain a "hyphen".
customElements.define('calc-app', CalcApp);

// helpers ------------------------------

// ⭐️ $(): select first element
function $(selector, parent = document){
  return parent.querySelector(selector);
}

// ⭐️ tag()
function tag(name, {
    textContent,
    attributes = {},
    parentNode,
}={}){

    // create element
    let elem = document.createElement(name);

    // set text content if necessary
    if (textContent) {
        elem.textContent = textContent;
    }
    
    // set attributes
    for (const [key, value] of Object.entries(attributes)) {
        elem.setAttribute(key, value);
    }

    // append to parent node if present
    if(parentNode){
        parentNode.appendChild(elem);
    }
    
    // return element
    return elem;
}
// Calculator State
const State = {
    good : 0,
    bad  : 1,
    error: 2,
};

// Model: Calculator
export default class Calculator {
    
    // init
    constructor(){
        this._memory = '';
        this._lastInput = undefined;
        this._debugMode = true;
    }

    // enter `key` into memory
    input(key){

        switch(key){
            case "×": key = '*'; break;
            case "+": key = '+'; break;
            case "−": key = '-'; break;
            case "÷": key = '/'; break;
        }

        switch(key){

            // clear
            case 'C': 
                this._memory = ''; 
                break;

            // execute
            case '=':
                this._memory = this.isCurrentResultValid
                    ? `${this._currentResult}`
                    : '💥 Error';
                break;

            // in the middle of entering
            default:
                // append `key` to memory
                this._memory += key;

                // ⭐️ if in error state, clear memory and start over.
                if (this.state == State.error) {
                    this._memory = key;
                }
        }
        
        this._lastInput = key;
        this._calcCurrentResult();

        if (this.debugMode) this.log();
    }

    // this.memory
    get memory(){
        return this._memory;
    }

    // this.debugMode
    get debugMode(){
        return this._debugMode;
    }

    // this.debugMode = true | false
    set debugMode(bool){
        this._debugMode = bool;
    }

    // this.isCurrentResultValid
    get isCurrentResultValid(){
        return Number.isFinite(this._currentResult);
    }

    // this.currentResult
    get currentResult(){
        return this._currentResult;
    }

    // this.log()
    log(){
        console.log(`memory: "${this.memory}", state: ${this.state}, result: ${this.currentResult}`);
    }

    // calculate current result based on `memory`
    _calcCurrentResult(){

        // empty memory
        if (this._memory == '') {
            this._currentResult = 0;
            return;
        }

        // memory not empty, try to calculate current result
        try {
            // if success, return the result
            const result = eval(this._memory);
            this._currentResult = result;
        } catch(e) {
            // else set current result `undefined`
            if (this.debugMode) console.log(`${e.name}: ${e.message}`);
            this._currentResult = undefined;
        }
    }

    // this.state
    get state(){
        // result is OK
        if (this.isCurrentResultValid) return State.good;
        // hit '=' and result is Not OK
        if (this._lastInput == '=') return State.error;
        // still in the middle of entering something
        return State.bad;
    }

    // this.inGoodState
    get inGoodState() {
        return this.state === State.good;
    }

    // this.inBadState
    get inBadState() {
        return this.state === State.bad;
    }

    // this.inErrorState
    get inErrorState() {
        return this.state === State.error;
    }

}
// ⭐️ import CalcApp first
import CalcApp from './calc-app.js';

// ⭐️ new <calc-app>
const app = new CalcApp('160px', '200px');

// calculator apps container
const div = $('.container');
div.append(app);

// helpers ------------------------------

// ⭐️ $(): select first element
function $(selector, parent = document) {
  return parent.querySelector(selector);
}
.container {
    display: inline-flex;
    gap: 10px;
}

.centered-element {

    margin: 0;
    /* border: 1px solid black; */
    
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}
  • replit ⟩ calculator (componet)

答案是:可以❗️但 export/import 還是要先做❗️ 👉🏻

simple calculator
Grid layout
Table layout
CodeSandbox
ES Modules