Javascript

Devdocs/JavaScriptopen in new window

QuickStart

JavaScript 是一个动态的弱类型语言

Ⅰ. 在 html 中引入 javascript

body 末尾导入(推荐)

<body>
	<script src="main.js"></script>
</body>

head 中导入

常用于导入外部包

<head>
	<script src="script.js"></script>
</head>
window.onload = function () {
	//...
}

Overview

Your First Program

console.log("Hello World!")
<script>
	document.write("Hello World!")
</script>

Variables

var num = 2 	  // 全局变量
let x = "Pol"     // 局部变量
const PI = 3.14   // 常量

Comments

// single line comment

/*
 this is a multi line comment
 */

/**
 * this is a document comment
 * @return string
 */

DataType

TIP

boolean:

false(0 / null / undefined / "")
var num = 12 			// number
var price = 42.3 		// number
var name = "Coulson" 	// string
var isActive = false 	// boolean

// 数组
var nums = [1, 2, 3, 4, 5]
// 实例对象(字典)
var person = {name: "Jack", age: 24}
// 集合
let set = new Set([1, 2, 4, 2, 59, 9, 4, 9, 1])
// Map
let map = new Map([
	["k1", "v1"],
	["k2", "v2"],
])

Input

// 暂不支持
var name = readLine()
var age = parseInt(readLine, 10) //string => int
var height = parseFloat(readLine(), 10)

Output

documents.write() // webpage, <br />换行, 其他'\n'
console.log() // 标准输出 OK
/* 弹窗 */
alert() // 提示框   OK
prompt() // 输入框   OK/Cannle -> value/null
confirm() // 确认框   OK/Cannel -> true/false
prompt & confirm
<script>var name = prompt("Please enter your name") alert(name)</script>
<script>
	var res = confirm("Are you sure ?") if (res) {alert("Closed")} else {alert("Stay...")}
</script>

Basic Concepts

Operators

===比较数据类型, ==比较值
console.log(5 ==  '5');  //true, 转换成同类型后比较值
console.log(5 === '5');  //false,先比较类型是否相同,严格模式

Conditional and Loops

If Statement

if (condition) {
	// statement(s)
} else if (condition) {
	// statement(s)
} else if (condition) {
	// statement(s)
} else {
	// statement(s)
}

Switch Statement

switch (expression) {
	case n1:
		// statement(s)
		break
	case n2:
		// statement(s)
		break
	default:
	// statement(s)
}
点击查看示例
var day = 2
switch (day) {
	case 1:
		console.log("Monday")
		break
	case 2:
		console.log("Tuesday")
		break
	case 3:
		console.log("Wednesday")
		break
	case 4:
		console.log("Thursday")
		break
	case 5:
		console.log("Friday")
		break
	case 6:
		console.log("Saturday")
		break
	case 7:
		console.log("Sunday")
		break
	default:
		console.log("Invalid day")
}

For Loops

for (let i = 0; i < arr.length; i++) {}
for (let v of str) {}
for (let v of list) {}
for (let v in dict) {}

arr.forEach(function (v) {})
arr.forEach((v) => {})
arr.forEach((v, i) => {})
// 索引越界 -> "undefined"
 








While Loop

while (condition) {
	// code block
}

Do While Loop

do {
	// code block
} while (condition)

Break & Continue

for (var i = 0; i < arr.length; i++) {
	if (arr[i] > 10) break
	console.log(arr[i])
}
for (var i = 0; i < arr.length; i++) {
	if (i == 5) continue
	console.log(arr[i])
}

Function

Define

function main() {
	// code
}

main()
const main = () => {
	// code
}

main()

单例模式

ES5 单例模式

var printArray = (function () {
	arr.forEach((v) => console.log(v))
})()

ES6 单例模式

const getDate = (() => {
	const d = new Date()
	const year = d.getFullYear()
	const month = d.getMonth() + 1
	const day = d.getDate()
	const date = `${year}-${month}-${day}`
	console.log(date)
})()

Params

function showInfo(name, age) {
	// code
}

showInfo("coulson", 20)

ES6 不同参数写法

const func = () => ...;
const func = x => ...;
const func = (x, y) => {}
const func = (nums) => {}
const func = (...nums) => {}
function test(a, b = 3, c = 42) {
	return a + b + c
}
const test = (a, b = 3, c = 42) => a + b + c

不定参

ES5 & ES6 不定参示例
function containsAll(arr) {
    for (var k = 1; k < arguments.length; k++) {
        var num = arguments[k];
        if (arr.indexOf(num) === -1) {
            return false;
        }
    }
    return true;
}
var x = [2, 4, 6, 7];
console.log(containsAll(x, 2, 4, 7));

 









//统计偶数和
const magic = (...nums) => {
    let sum = 0;
    nums.filter(n => n%2==0).map(el => sum += el);
    return sum;
}
console.log(magic(...nums));

 





Return

function showInfo(name, age) {
	// code
	return `name: ${name}\nage: ${age}`
}

console.log(showInfo("coulson", 20))
/*
name: coulson
age: 20
*/

TIP

undefined: 函数没有返回值

function check(name) {
	var res = "My name is " + name
}

console.log(check("coulson"))
// Output: undefined

闭包

闭包是指有权访问另外一个函数作用域中的变量的函数 在 Javascript 中,如果一个对象不再被引用,那么这个对象就会被 GC 回收,否则这个对象一直会保存在内存中 当我们需要在模块中定义一些变量,并希望这些变量一直保存在内存中但又不会 “污染” 全局的变量时,就可以用闭包来定义这个模块

<p>局部变量计数。</p>
<button type="button" onclick="myFunction()">计数!</button>
<p id="demo">0</p>

<script>
	const add = (function () {
		let counter = 0
		return function () {
			return (counter += 1)
		}
	})()

	function myFunction() {
		document.getElementById("demo").innerHTML = add()
	}
</script>

Object

Define

// 定义实例对象(初始化对象)
var person = {
	name: "John",
	age: 20,
}

// 访问对象数据
console.log(person[age]) // 20
console.log(person.age) // 20
console.log(person.name) // "John"
console.log(person.name.length) // 4

Create Object

function person(name, age) {
	this.name = name // this指代当前对象
	this.age = age
}

var p = new person("John", 20)
console.log(`name: ${p.name}, age: ${p.age}`)

Object Methods

function person(name, age) {
	this.name = name // this指代当前对象
	this.age = age
    this.setName = function(name) {
        this.name = name
    }
}

var p = new person("John", 20)
p.setName("Tom")        // name: Tom




 
 
 



 

function person(name, age) {
	this.name = name // this指代当前对象
	this.age = age
	this.setName = changeName
}

function changeName(name) {
    return this.name = name
}

var p = new person("John", 20)
p.setName("Tom")
console.log(p.name)     // name: Tom



 


 
 
 


 

Core Objects

1. String

let str1 = "Hello, React !"
let str2 = new String("Hello, React !")
/**
 * ==: 比较值相等
 * ===: 比较值和类型同时相等
 */

let str = "Hello, React !"
let str1 = new String("Hello, React !")
let str2 = new String("Hello, React !")

console.log(str1 == str)   // true:  值相等
console.log(str1 === str)  // false: 类型不同,对象与字符串
console.log(str1 == str2)  // false: 对象无法比较值
console.log(str1 === str2) // false: 对象无法比较值
/**
 * 索引
 * indexOf(): 可以使用第二个参数指定开始索引
 * lastIndexOf()
 * search(): 可以使用正则
 * match()
 * replace(): 可以使用正则, 替换字符
 */

let str = "Hello, React !"
console.log(str.indexOf("e")) 		// Output: 1
console.log(str.lastIndexOf("e"))   // Output: 8
console.log(str.indexOf("e", 2))    // Output: 8

let newStr = "Hello, 3306 world! "
console.log(newStr.search(/\d+/g))  	 // Output: 7
console.log(newStr.replace(/\d/g, ""))	 // Hello, world!

var str = "hello3306world8080pop"
console.log(str.match(/\d+/g))     				  // ['3306', '8080']
console.log(str.replace(/(\w+)\s(\w+)/, "$2, $1")) // Hello, React
/**
 * slice(start, end): 可以使用负数
 * substring(start, end)
 * substr(start, length): 已移除不推荐使用
 */

let str = "Hello, React !"

console.log(str.slice(-5, -1))   // act
console.log(str.slice(3)) 		 // lo, React !
console.log(str.slice(3, 5)) 	 // lo
console.log(str.substring(3, 5)) // lo
console.log(str.substr(3, 5))    // lo, R
example
let str = "hello world"

// 查询
console.log(str)
console.log(typeof str)              // string
console.log(typeof str === "string") // true
console.log(str.length)              // 11
console.log(str.charAt(0))           // h
console.log(str.indexOf("w"))        // 6 (不存在返回-1)
console.log(str.substring(3, 5))     // lo
console.log(str.slice(3, 5))         // lo
// 修改
console.log(str.concat("!"))         // hello world!
console.log(str.split(" "))          // ["hello", "world"]
console.log(str.replace("world", "earth"))   // "hello earth"
console.log(" hello world ".trim())   // "hello world"
console.log("7".padStart(2, "0"))     // "07"
console.log("3.14".padEnd(5, "0"))    // "3.140"
str2 = "hello3306world8080pop"
console.log(str2.match(/\d+/g))       // [ '3306', '8080' ]
// 判断
console.log(str.includes("world"))    // true
console.log(str.startsWith("hello"))  // true
console.log(str.endsWith("world"))    // true
console.log(str.toUpperCase())        // HELLO WORLD
console.log(str.toLowerCase())        // hello world


2. Array

var arr = new Array("HTML", "CSS", "JS", "C++")		// old
let arr = ["HTML", "CSS", "JS", "C++"]				// new
// 查
.length
.slice(-1)                 // 返回元素,切片
.indexOf(e, [startIndex])  // 返回索引, 不存在则返回-1
.includes(e)               // true or fasle

// 增删
.push(e)      // 末尾添加
.pop()        // 末尾删除
.unshift(e)   // 首部添加
.shift()      // 首部删除
arr.sort((a,b) => a-b)  	// 从小到大
arr.sort((a,b) => b-a)  	// 从大到小
arr.sort()              	// 按字符名称排序
arr.sort((a, b) => b.date.localeCompare(a.date))	// 日期就近排序
 
 


arr.reverse()
// 数组---字符串
arr.join('')     // 数组->字符串,默认','
str.split('')    // 字符串->数组
Array.form(str, [func])  // 字符串->数组
console.log(Array.from({length: 5}, (v, i) => i))	// [0,1,2,3,4,5]

// 数组---数组
arr.concat()            // 数组合并,返回一个新数组
arr.map(v => v + 2)     // 返回新数组
[...arr, arr2]

// 遍历
arr.forEach((item, index) => {...})
arr.filter(n => n%2==0).map(el => sum += el); //统计偶数和

// 删除指定元素
arr.filter(item => item != e)

// 去重
Array.from(new Set(arr))

arr.filter((v, i, self) => self.indexOf(v) === i)

function uniqueArr() {
	let res = []
	for(let i = 0; i < arr.length; i++) {
		if !res.includes(arr[i]) res.push(arr[i])
	}
	return res
}
// 数组判断
let arr = [1, 2, 3]
console.log(Array.isArray(arr))		// true, 推荐此方法
console.log(arr instanceof Array)	// true

console.log(typeof arr)  // object
console.log(typeof {})	 // object
console.log(typeof null) // object


 
 




数组原地排序的区别
let arr = [1, 5, 3, 9, 2, 10, 12]
arr.sort((a, b) => a - b)	// [1, 2, 3, 5, 9, 10, 12]
arr.sort((a, b) => b - a)	// [12, 10, 9, 5, 3, 2, 1]
arr.sort()					// [1, 10, 12, 2, 3, 5, 9]
arr.map()、for loop、forEach()的区别

arr.map(): 返回新数组

var nums = [2, 3, 5, 1]
console.log(nums.map((v) => v + 2)) // [4, 5, 7, 3]
console.log(nums.map((v) => (v = v + 2)))
console.log(nums) // [2, 3, 5, 1]
console.log(nums.map((v, i) => v + i)) // [2, 4, 7, 4]

for Loop: 改变原数组

var nums = [2, 3, 5, 1]
for (var i = 0; i < nums.length; i++) {
	nums[i] += 2
}
console.log(nums) // [4, 5, 7, 3]

forEach(): 不直接改变原数组,需要通过arr[i]才能改变原数组

// forEach(v => {}): 不改变原数组
nums.forEach((v) => (v += 2))
console.log(nums) //[ 2, 3, 5, 1 ]

// forEach((v, i) => {}): 不直接改变原数组,需要通过arr[i]才能改变原数组
nums.forEach((v, i) => (nums[i] += 2))
console.log(nums) //[ 4, 5, 7, 3]
join(), split(), Array.from()
console.log("foo".split()) // ["foo"]
console.log("foo".split("")) // ["f", "o", "o"]

console.log(Array.from("foo")) // ["f", "o", "o"]
console.log(Array.from([1, 2, 3], (x) => x + x)) // 2, 4, 6]

console.log(["f", "o", "o"].join()) // "f,o,o"
console.log(["f", "o", "o"].join("")) // "foo"

3. Map

let map = {}
let map = new Map()
let map = new Map([
	["k1", "v1"],
	["k2", "v2"],
])
// 查
.size
.get(key)
.has(key)
.set(key, value)	// 增
.delete(key)		// 删
.clear()

.keys()
.values()
.entries()
for (let key in map) {
	// code
}


map.forEach((k, v) => console.log(k, v))
/*
v1 k1
v2 k2
*/
for (let item of map.entries()) {
	console.log(item)
}
/*
[ 'k1', 'v1' ]
[ 'k2', 'v2' ]
*/

for (let item of map.entries()) {
	console.log(item[0], item[1])
}
/*
k1 v1
k2 v2
*/
查看 Map 示例
let map = new Map([
	["k1", "v1"],
	["k2", "v2"],
])

console.log(map) // Map(2) { 'k1' => 'v1', 'k2' => 'v2' }
map.forEach((k, v) => console.log(k, v))
for (let item of map.entries()) {
	console.log(item)
}
/*
[ 'k1', 'v1' ]
[ 'k2', 'v2' ]
*/

for (let item of map.entries()) {
	console.log(item[0], item[1])
}
/*
k1 v1
k2 v2
*/

map.set("k3", "v3").set("k4", "v4")
map.delete("k3")
map.clear()
console.log(map.get("k2")) // undefined
console.log(map.has("k3")) // false

4. 字典

let a = {x: 1, x: 2, x: 3, x: 4}
console.log(a.x) // 4
//ES6: 对象实例化
const person = {
    name: 'Jack',
    age: 24,
    sex: 'male',
    getBirthyear() {
        return (2021 - this.age)
    }
};
// 字典(类的实例)
for (let v in person) {console.log(v)}
console.log(person.getBirthyear());
-------------------------------
//解构
let prop = 'name';
let id = '1234';
let mobile = '08923';
let user = {
  [prop]: 'Jack',
  [`user_${id}`]: `${mobile}`
};
-------------------------------
//实例类继承  (后者覆盖前者值,合并独立值)
let student = {name: 'Bob', age: 20, xp: '2'};
let newStudent = Object.assign({}, person, student);
console.log(newStudent.name, newStudent.age, newStudent.sex, newStudent.xp);   //Bob 20 male 2
const obj = {
	{ id: 1, name: 'john', country: 'cn' },
	{ id: 2, name: 'mary', country: 'us' },
	{ id: 3, name: 'yart', country: 'cn' },
	{ id: 4, name: 'lkwq', country: 'us' },
	{ id: 5, name: 'qwqw', country: 'cn' },
}

function filterData(query) {
	// const res = obj.filter((item) => item.name.includes(query)).map(item => item.name)
	const k = 'name'
	const res = obj.filter((item) => item[k].includes(query)).map(item => item[k])
}

5. Set

创建集合

let set = new Set([1, 2, 4, 2, 59, 9, 4, 9, 1])
let set = new Set()

API

TIP

支持链式编程

//API
长度:size
增:add(v)
删:delete(v)
删:clear()
查:has(v)
查:values()
遍历: forEach() or for(let v of set)
查看 Set 示例
let set = new Set([1, 2, 2, 4, 3, 1])
// 增
set.add(5).add(7)
// 删
set.delete(2)
set.clear()
// 查
console.log(set.has(9))

// 集合遍历1
set.forEach((v) => console.log(v))
// 集合遍历2
for (let v of set.values()) {
	console.log(v)
}

6. Object

Object.keys(obj)
Object.values(obj)
const obj = {
	title: "项目名称",
	dataIndex: "projectName",
	fixed: "left",
	width: 100,
}

console.log(Object.keys(obj))
// Output: [ 'title', 'dataIndex', 'fixed', 'width' ]

// 通过对象值返回对应的键
function getkeyByvalue(object, value) {
	return Object.keys(object).find((key) => object[key] === value)
}

console.log(obj, "left")
const data = [
	{id: "1", name: "A", rank: 0},
	{id: "2", name: "B", rank: 1},
	{id: "3", name: "C", rank: 1},
	{id: "4", name: "D", rank: 2},
]

let res = data.map((item) => (item = {id: item.id, name: item.name}))
console.log(res)

库函数

Math

Math.PI / E / LN2 / LN10 / LOG2E / LOG10E
Math.abs(x) / sqrt(x) / ceil(x) / round(x) / floor(x) / exp(x) / pow(x, y) / random()
Math.random() // [0-1) 之间的随机小数

Date

setInterval()
clearInterval()
setTimeOut()
function printTime() {
	var d = new Date()
	var year = d.getFullYear()
	var month = d.getMonth() + 1
	var day = d.getDate()
	var hours = d.getHours()
	var mins = d.getMinutes()
	var secs = d.getSeconds()
	var weekDay = ["日", "一", "二", "三", "四", "五", "六"]
	var week = "周" + weekDay[d.getDay()]
	document.body.innerHTML = hours + ":" + mins + ":" + secs
}
setInterval(printTime, 1000)

JSON

// 将json字符串转换成json对象
JSON.parse(str)
eval("(" + str + ")")

// 将json对象转换为json字符串
JSON.stringify(obj)
// 美化输出
JSON.stringify(obj, null, 2)

ES6

区别与 ES5(2015 年以前的 javascript 语法)

var & let & const

// ES5
var num = 12
// ES6
let age = 20
const PI = 3.14

Format Output

let name = "John"
// ES5
var msg = "Hello" + name + "!"
// ES6
let msg = `Hello ${this.name}!`

Object

let person = {
	name: "John",
	age: 20,
	sex: "male",
}

let student = {
	name: "Bob",
	age: 18,
	xp: "2",
}

let s = Object.assign({}, person, student)
console.log(s.name) // "Bob"
console.log(s.age) // 18
console.log(s.sex) // "male"
console.log(s.xp) // "2"

解构

let arr = [1, 2, 3]
let [a, b, c] = arr
// a: 1, b: 2, c: 3
let person = {
	name: "John",
	age: 20,
}

let {name, s} = person
console.log(name) // "John"
console.log(age) // 20

Rest Params

const magic = (...nums) => {
	let sum = 0
	nums.filter((n) => n % 2 == 0).map((el) => (sum += el))
	return sum
}
console.log(magic(...nums))
function containsAll(arr) {
	for (var k = 1; k < arguments.length; k++) {
		var num = arguments[k]
		if (arr.indexOf(num) === -1) {
			return false
		}
	}
	return true
}
var x = [2, 4, 6, 7]
console.log(containsAll(x, 2, 4, 7))

Spread Operator

function func(a, b, c) {
	console.log(a + b + c)
}

let nums = [1, 2]
func(nums.concat(4)) // 1,2,4undefinedundefined
func(1, 2, 4) // 7
func.apply(null, nums) // NaN
func.apply(null, [1, 2, 4]) // 7
func.apply(null, nums.concat(4)) // 7
func(...nums, 4) // 7
let arr = [4, 6, ...nums, 9] // [4, 6, 1, 2, 9]
const sum = (...nums) => {
	let sum = 0
	arr.forEach((v) => (sun += v))
	return sum
}

console.log(sum(1, 2, 3)) // 6
console.log(sum(3, 6, 7, 9)) // 25

ES6 Class

class Person {
	constructor(name, age) {
		this.name = name
		this.age = age
	}

	say() {
		console.log(`hi, my name is ${this.name}.`)
	}

	// say = () => console.log(`hi, my name is ${this.name}.`);

	/* 静态成员方法 */
	static staticSay = (name) => console.log(`hi, my name is ${name}.`)
}

/* 类的继承 */
class Student extends Person {
	constructor(name, number) {
		super(name)
		this.number = number
	}

	/* 方法重写 */
	say() {
		console.log(`hey, I'm ${this.name}.`)
	}

	hello() {
		// 调用父类方法
		super.say()
		console.log(`my school number is ${this.number}.`)
	}
}

/*
 * 创建类对象
 * const p = new Person("tom")
 * p.say()
 *
 * 调用类的静态方法
 * Person.staticSay("coke")
 *
 * 继承
 * const s = new Student("jacker", "1002")
 * s.hello()
 */

constructor

class Rectangle {
	constructor(height, width) {
		this.height = height
		this.width = width
	}
}

不定参

class Polygon {
	constructor(...sides) {
		this.sides = sides
	}
	// Method
	*getSides() {
		for (const side of this.sides) {
			yield side
		}
	}
}

const pentagon = new Polygon(1, 2, 3, 4, 5)

console.log([...pentagon.getSides()]) // [1,2,3,4,5]

示例

class Polygon {
	constructor({name = "pole", age}) {
		this.name = name
		this.age = age
	}
	sayHello = () => {
		console.log(`Hello, ${this.name} I'm ${this.age} years old.`)
	}
}

const option = {
	age: 23,
}
const poly = new Polygon(option)
poly.sayHello()

static

class Person {
	constructor(name) {
		this.name = name
	}

	say() {
		return `Hello, ${this.name}`
	}
}

const person = new Person("John")
console.log(person.say())
// Output: "Hello, John"

静态变量和方法

class Person {
	static name = "John"
	static staticSay = () => {
		return `Hello, ${this.name}`
	}
}

console.log(Person.name)
// Output: "John"
console.log(Person.staticSay())
// Output: "Hello, John"

private

class PrivateStaticField {
	static #PRIVATE_STATIC_FIELD

	static #privateMethod() {
		return "hello world"
	}

	static basePublicStaticMethod() {
		this.#PRIVATE_STATIC_FIELD = 42
		return this.#PRIVATE_STATIC_FIELD
	}
}

console.log(PrivateStaticField.publicStaticMethod())
// Output: 42
private
class Base {
	static #privateStaticMethod() {
		return 42
	}
	static publicStaticMethod1() {
		return Base.#privateStaticMethod()
	}
	static publicStaticMethod2() {
		return this.#privateStaticMethod()
	}
}

class Derived extends Base {}

console.log(Base.publicStaticMethod1())
// 42
console.log(Base.publicStaticMethod2())
// 42

console.log(Derived.publicStaticMethod1())
// 42
console.log(Derived.publicStaticMethod2())
// Error: Receiver must be class Base

getter & setter

TIP

get func() {}
set func() {}
class ClassWithPrivateAccessor {
	#message

	get #decoratedMessage() {
		return `🎬${this.#message}🛑`
	}
	set #decoratedMessage(msg) {
		this.#message = msg
	}

	constructor() {
		this.#decoratedMessage = "hello world"
		console.log(this.#decoratedMessage)
	}
}

new ClassWithPrivateAccessor()
// 🎬hello world🛑

Map & Set

Ⅴ. html 与 javascript 交互

parent
   |______ child
   |______ child



window.onload = function() {};

// SelectElement
document.getElementById()
document.getElementsByClassName()
document.getElementsByTagName()

document.querySelector()
document.querySelectorAll()

// Create & Remove Element
document.createElement()
el.removeElement()


// HTML
el.innerHTML
el.innerText
document.createTextNode()   //创建文本

el.value
el.src
el.href
...
el.setAttribute("src", "https://mimo.app/r/kittles.png")
el.getAttribute("href")

el.classList.add("active")
el.classList.remove("active")
el.classList.toggle("active")

// CSS
el.style.color = "red"
el.style.backgroundColor = "skyblue"



// 子节点
//查
el.childNodes
el.firstChild
el.lastChild
el.firstElementChild
el.lastElementChild
el.perviousSibling
el.nextSibling
el.parentNode
el.hasChildNodes()
//增删改
.appendChild()
.removeChild()
.replaceChild(new, old)



// Button Event
btn.addEventListener("click", func, true);
btn.removeEventListener("click", func);
click
dbclick
scroll
input
change
mouseover
mousemove
mouseenter
mouseleave
mousedown
mouseup
pointermove


setInterval(func, 100);
setTimeout (func, 500);
clearInterval(setInterval(func, 100))

1.子节点

<nav class="main-nav">
    <ul class="main-menu">
    <li>Office</li>
    <li>Windows</li>
    <li>Surface</li>
    <li>Xbox</li>
    <li>Deals</li>
    <li>Support</li>
    </ul>
</nav>
<script>
    window.onload = function() {
        var arr = []
        var el = document.querySelector(".main-menu")
        var childs = el.childNodes
        for (var i = 0; i < childs.length; i++) {
            if(i % 2 !== 0) arr.push(childs[i].innerHTML)
        }
        console.log(arr)
    }
</>

2. js 动画

#container {
	width: 200px;
	height: 200px;
	background: green;
	position: relative;
}
#box {
	width: 50px;
	height: 50px;
	background: red;
	position: absolute;
}
window.onload = function () {
	var pos = 0
	//our box element
	var box = document.getElementById("box")
	var t = setInterval(move, 10)

	function move() {
		if (pos >= 150) {
			clearInterval(t)
		} else {
			pos += 1
			box.style.left = pos + "px"
		}
	}
}

3. 表单验证

<form onsubmit="return validate()" method="post">
	Number:
	<input type="text" name="num1" id="num1" />
	<br />
	Repeat:
	<input type="text" name="num2" id="num2" />
	<br />
	<input type="submit" value="Submit" />
</form>

<script>
	function validate() {
		var n1 = document.getElementById("num1")
		var n2 = document.getElementById("num2")
		if (n1.value != "" && n2.value != "") {
			if (n1.value == n2.value) {
				return true
			}
		}
		alert("The values should be equal and not blank")
		return false
	}
</script>

4. active 按钮切换

<div class="side-menu">
	<a class="sidebar-link discover is-active">
		<svg></svg>
		Discover
	</a>
	<a class="sidebar-link trending">
		<svg></svg>
		Trending
	</a>
	<a class="sidebar-link streaming">
		<svg></svg>
		Streaming
	</a>
</div>
$(".sidebar-link").click(function () {
	$(".sidebar-link").removeClass("is-active")
	$(this).addClass("is-active")
})

5. 滚动页面 header 阴影切换

<div class="header"></div>
<div class="wrapper"></div>

<style>
	.header-shadow {
		box-shadow: 0 4px 20px rgb(88 99 148 / 17%);
		z-index: 1;
	}
</style>

<script>
	const wrapper = document.querySelector(".wrapper")
	const header = document.querySelector(".header")
	wrapper.addEventListener("scroll", (e) => {
		e.target.scrollTop > 30 ? header.classList.add("header-shadow") : header.classList.remove("header-shadow")
	})
</script>
function scrollHeader() {
	const header = document.getElementById("header")
	// When the scroll is greater than 50 viewport height, add the scroll-header class to the header tag
	if (this.scrollY >= 50) header.classList.add("scroll-header")
	else header.classList.remove("scroll-header")
	// this.scrollY >= 50 ? header.classList.add('scroll-header') : header.classList.remove('scroll-header')
}
window.addEventListener("scroll", scrollHeader)
.scroll-header {
	box-shadow: 0 1px 4px hsla(0, 4%, 15%, 0.1);
}
<header class="header" id="header"></header>

Ⅵ 架构

1.html 结构

app
 |____header
 |       |____logo
 |       |      |___svg
 |       |      |___Title
 |       |____header-menu
 |       |         |______<a href="#" class="menu-link is-active"></a>
 |       |         |______a
 |       |         |______a
 |       |____user-settings
 |
 |____wrapper
         |____main-container
                       |____content-wrapper
                                    |_______content-section


<div class="app">
	<div class="header">
		<div class="logo">
			<svg></svg>
			Title
		</div>
		<div class="header-menu">
			<a href="#" class="menu-link is-active">Home</a>
			<a href="#" class="menu-link">About</a>
			<a href="#" class="menu-link">Concact</a>
		</div>
		<div class="user-settings">
			<div class="dark-light"></div>
		</div>
	</div>
	<div class="wrapper">
		<div class="main-container">
			<div class="content-wrapper">
				<div class="content-section"></div>
			</div>
		</div>
	</div>
</div>
<!-- 主题切换按钮svg -->
<svg viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
	<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" />
</svg>

2.css 样式

@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap");
* {
	margin: 0;
	padding: 0;
	box-sizing: border-box;

	font-family: "Poppins", sans-serif;
	outline: none;
	list-style: none;
	background-repeat: no-repeat;
}
/*
body {
	display: flex;
	justify-content: center;
    align-items: center;
	min-height: 100vh;
}
*/

/* 顶部导航栏 */
.header {
	display: flex;
	justify-content: space-between;
	align-items: center;
	flex-shrink: 0;

	padding: 0 40px;
	white-space: nowrap;

	height: 60px;
	width: 100%;
	background-color: var(--header-bg-color);

	font-size: 14px;
	transition: box-shadow 0.3s;
}
.header-shadow {
	box-shadow: 0 4px 20px rgb(88 99 148 / 17%);
	z-index: 1;
}

/* 主题背景切换按钮svg */
.dark-light svg {
	margin-right: 8px;
	width: 22px;
	cursor: pointer;
	fill: transparent;
	transition: 0.5s;
}

.dark-mode .dark-light svg {
	fill: #ffce45;
	stroke: #ffce45;
}

3.js 事件

window.onload = function () {
	// header阴影滚动显示
	const wrapper = document.querySelector(".wrapper")
	const header = document.querySelector(".header")
	wrapper.addEventListener("scroll", (e) => {
		e.target.scrollTop > 30 ? header.classList.add("header-shadow") : header.classList.remove("header-shadow")
	})

	// 按钮切换active
	$(function () {
		$(".menu-link").click(function () {
			$(".menu-link").removeClass("is-active")
			$(this).addClass("is-active")
		})
	})

	// dark-light点击按钮切换主题背景色
	const toggleButton = document.querySelector(".dark-light")
	toggleButton.addEventListener("click", () => {
		document.body.classList.toggle("dark-mode")
	})
}

DOM

GetElements

document.querySelector()
document.querySelectorAll()
document.getElementById(id)
document.getElementsByTagName(name)
document.getElementsByClassName(classname)

SetElements

element.innerHTML = new html content
element.attribute = new value
element.setAttribute(attribute, value)
element.style.property = new style

AddElement & RemoveElement

document.createElement(element)
document.removeChild(element)
document.appendChild(element)
document.replaceChild(element)

SubElements

childNodes[0]

Event

document.querySelector(id).onclick = function(){
	...
}

document.querySelector(id).click(function() {
	...
})

document.querySelector(id).on("click", function() {
	...
})

document.querySelector(id).addEventListener("click", function(){
	...
})
document.querySelector(id).removeEventListener()

mouseover
mouseout
mousedown
mouseup
click
change
focus
onload
resize
scroll












 














浏览器宽高

(浏览器窗口(浏览器视口)不包括工具栏和滚动条)

<p id="demo"></p>

<script>
	var w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth

	var h = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight

	var x = document.getElementById("demo")
	x.innerHTML = "浏览器内窗宽度:" + w + ",高度:" + h + "。"
</script>
window.addEventListener("scroll", function () {})

window.innerWidth
window.innerHeight
// 滚动距离
window.scrollY
window.pageYOffset
window.open() - 打开新窗口
window.close() - 关闭当前窗口
window.moveTo() - 移动当前窗口
window.resizeTo() - 重新调整当前窗口
window.scroll({top: 0})
window.addEventListener("scroll", function () {
  if (window.scrollY >= 80) {
	  ...
  }
})

置顶按钮

const scrollToTop = document.querySelector(".scrollToTop")

window.addEventListener("scroll", () => {
	window.pageYOffset > 400 ? (scrollToTop.style.display = "block") : (scrollToTop.style.display = "none")
})
scrollToTop.addEventListener("click", () => {
	window.scroll({top: 0})
})
screen.width
screen.height
screen.availWidth
screen.availHeight
screen.colorDepth
screen.pixelDepth
document.cookie

html 与 javascript 交互

window.onload = function () {}

/*
document.createElement()
document.createTextNode()   //创建文本

document.getElementById()
document.getElementsByClassName()
document.getElementsByTagName()
*/

/*
innerHTML	 //html内容
childNodes   //html元素

appendChild()
removeChild()
replaceChild(new, old)
*/

btn.onclick = function () {}
;<button onclick="show();">Click Me</button>

btn.addEventListener("click", myFunction, true)
btn.removeEventListener("click", myFunction)

setInterval(func, 100)
setTimeout(func, 500)

1. 更改子元素内容

/** * document.getElementById() * .childNodes * .innerHTML * setTimeout(func, 500); */

<div id="demo">
	<p>some text</p>
	<p>some other text</p>
</div>

<script>
	function setText() {
		var a = document.getElementById("demo")
		var arr = a.childNodes
		for (var x = 0; x < arr.length; x++) {
			arr[x].innerHTML = "new text"
		}
	}
	setTimeout(setText, 500)
</script>

/* new text new text */

2. 更改元素 src 属性值 (更换图片)

<img id="mying" src="orange.png" alt="" />

<script>
	var e = document.getElementById("mying")
	e.src = "apple.png"
</script>

3. 更改样式颜色

/* * window.onload = function() {}; * arr[i].style.color = "#33EA73"; */

<div>
	<span>...</span>
	<span>...</span>
</div>

<script>
	window.onload = function() {
	    var el = document.getElementsByTagName('span');
	    for(int i = 0; i < el.length; i++){
	        el[i].style.color = "#33EA73";
	    }
	};
</script>
<div id="demo" style="width:200px">some text</div>

<script>
	window.onload = function () {
		var x = document.getElementById("demo")
		x.style.color = "#6600FF"
		x.style.width = "100px"
	}
</script>

4. 新建 html 段落

<div id="demo">some content</div>

<script>
	window.onload = function () {
		//creating a new paragraph
		var p = document.createElement("p")
		var node = document.createTextNode("Some new text")
		//adding the text to the paragraph
		p.appendChild(node)
		var div = document.getElementById("demo")
		//adding the paragraph to the div
		div.appendChild(p)
	}
</script>

/* some content Some new text */

5. js 动画

<div id="container">
	<div id="box"></div>
</div>
#container {
	width: 200px;
	height: 200px;
	background: green;
	position: relative;
}
#box {
	width: 50px;
	height: 50px;
	background: red;
	position: absolute;
}
window.onload = function () {
	var pos = 0
	//our box element
	var box = document.getElementById("box")
	var t = setInterval(move, 10)

	function move() {
		if (pos >= 150) {
			clearInterval(t)
		} else {
			pos += 1
			box.style.left = pos + "px"
		}
	}
}

6. 鼠标点击事件

<button onclick="show();">Click Me</button>

<script>
	function show() {
		alert("Hi there")
	}
</script>
<button id="demo">Click Me</button>

<script>
	window.onload = function () {
		var x = document.getElementById("demo")
		x.onclick = function () {
			document.body.innerHTML = Date()
		}
	}
</script>
window.onload = function () {
	var btn = document.getElementById("demo")
	btn.addEventListener("click", myFunction, true)
	function myFunction() {
		alert(Math.random())
		btn.removeEventListener("click", myFunction)
	}
}

7. 验证登录

<form onsubmit="return validate()" method="post">
	Number:
	<input type="text" name="num1" id="num1" />
	<br />
	Repeat:
	<input type="text" name="num2" id="num2" />
	<br />
	<input type="submit" value="Submit" />
</form>

<script>
	function validate() {
		var n1 = document.getElementById("num1")
		var n2 = document.getElementById("num2")
		if (n1.value != "" && n2.value != "") {
			if (n1.value == n2.value) {
				return true
			}
		}
		alert("The values should be equal and not blank")
		return false
	}
</script>

BOM

window

window.addEventListener("scroll", () => {})
// 置顶
window.scroll({top: 0})
window.pageYOffset

screen

// 全屏
document.documentElement.requestFullscreen()
// 退出全屏
document.exitFullscreen()
<button class="btn" id="fullscr">Go Fullscreen</button>

<script>
	let fullscreen
	let fsEnter = document.getElementById("fullscr")

	fsEnter.addEventListener("click", function (e) {
		e.preventDefault()
		if (!fullscreen) {
			fullscreen = true
			document.documentElement.requestFullscreen()
			fsEnter.innerHTML = "Exit Fullscreen"
		} else {
			fullscreen = false
			document.exitFullscreen()
			fsEnter.innerHTML = "Go Fullscreen"
		}
	})
</script>










 



 




document.documentElement

document.documentElement.scrollTop
document.documentElement.scrollHeight
document.documentElement.clientHeight
document.documentElement.clientWidth

// [0, 1]
let scrolled = document.documentElement.scrollTop / (document.documentElement.scrollHeight - document.documentElement.clientHeight)

三者都是临时存储客户端会话信息或者数据的方法

  1. 存储的时间有效期不同:
  • cookie的有效期是可以设置的,默认的情况下是关闭浏览器后失效
  • sessionStorage: 仅保持在当前页面,关闭当前会话页或者浏览器后就会失效, 键值对会被清空
  • localStorage: 永久性存储, 直到手动删除
  1. 存储的大小不同
  • cookie的存储是4kb左右,存储量较小,一般页面最多存储20条左右信息
  • localStorage和sessionStorage的存储容量是5Mb
  1. 与服务端的通信
  • cookie会参与到与服务端的通信中,一般会携带在http请求的头部中,例如一些关键密匙验证等。
  • localStorage和sessionStorage是单纯的前端存储,不参与与服务端的通信
  1. 读写操作的便捷程度
  • cookie的相关操作,cookie操作起来较为繁琐,并且部分数据不可以读取操作
  1. 对于浏览器的支持
  • cookie出现的时间较早,目前见到的浏览器都支持
  • localStorage和sessionStorage出现的时间较晚,对于版本较低的浏览器不支持(比如IE8版本以下的都不支持)
// cookie
document.cookie="username=John Doe; expires=Thu, 18 Dec 2043 12:00:00 GMT";
console.log(document.cookie)
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 GMT";

// storage
localStorage.setItem("key", JSON.stringify(obj))
localStorage.getItem("key")
localStorage.removeItem('key')
localStorage.clear()

Module

Date

获取日期格式
function printTime() {
	var d = new Date()
	var year = d.getFullYear()
	var month = d.getMonth() + 1
	var day = d.getDate()
	var hours = d.getHours()
	var mins = d.getMinutes()
	var secs = d.getSeconds()
	// var mins = mins < 10 ? `0${mins}` : mins;
	// var secs = secs < 10 ? `0${secs}` : secs;
	var date = `${year}-${month}-${day}`
	var time = `${hours}:${mins}:${secs}`
	console.log(date + " " + time)

	// var dateTime = hours < 12 ? `${date} ${time} AM` : `${date} ${time} PM`;
	// var weekDay = ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"];
	// var dateTime = hours < 12 ? `${date} ${time} AM` : `${date} ${time} PM ${weekDay[d.getDay()]}`;
	// console.log(dateTime);      //2022-1-25 9:49:04 AM

	// $("#cmp4c744dlabel").html(dateTime);
}
setInterval(printTime, 1000)

const dateTime = new Date()
console.log(dateTime.toLocaleString()) // 2022/1/12 下午1:45:36
// console.log(dateTime.toDateString()) // Tue Jan 25 2022
示例
/**
 * 日期格式化
 */
function getTime({format=false, displayTime=false, displayWeek=false} = {}) {
	const d = new Date()
	let [month, day, year] = [d.getMonth() + 1, d.getDate(), d.getFullYear()]
	let [hours, mins, secs] = [d.getHours(), d.getMinutes(), d.getSeconds()]
	let weekDay = ["日", "一", "二", "三", "四", "五", "六"]
	let week = ''
    // 格式化日期时间
	if (format) {
		month = month < 10 ? `0${month}` : month
		day = day     < 10 ? `0${day}`   : day
		hours = hours < 10 ? `0${hours}` : hours
		mins = mins   < 10 ? `0${mins}`  : mins
		secs = secs   < 10 ? `0${secs}`  : secs
		week = hours < 12 ? `AM` : `PM`
	}

	let date = `${year}-${month}-${day}`
	let time = `${hours}:${mins}:${secs}`

	// 1. 默认日期: 年-月-日
	res = date

	// 2. 显示具体时间:时-分-秒
	if (displayTime) res = `${date} ${time}`

	// 3. 显示上午还是下午,星期几
	if (displayWeek) {
		res = `${date} ${time} ${week} 星期${weekDay[d.getDay()]}`
	}

	return res
}

module.exports = {getTime}
// exports.getTime() {}




 



































/*
const date = require("./DateTime")
console.log(date.getTime())
*/

const option = {
	format: true,
	displayTime: false,
	displayWeek: true,
}

const {getTime} = require("./DateTime")
console.log(getTime())
console.log(getTime(option))
/**
 * 日期格式化
 */

class DateTime {
	constructor({format = false, displayTime = false, displayWeek = false} = {}) {
		;(this.format = format), (this.displayTime = displayTime), (this.displayWeek = displayWeek)
	}
	getTime() {
		const d = new Date()
		let [month, day, year] = [d.getMonth() + 1, d.getDate(), d.getFullYear()]
		let [hours, mins, secs] = [d.getHours(), d.getMinutes(), d.getSeconds()]
		let weekDay = ["日", "一", "二", "三", "四", "五", "六"]
		let week = ""
		// 格式化日期时间
		if (this.format) {
			month = month < 10 ? `0${month}` : month
			day = day < 10 ? `0${day}` : day
			hours = hours < 10 ? `0${hours}` : hours
			mins = mins < 10 ? `0${mins}` : mins
			secs = secs < 10 ? `0${secs}` : secs
			week = hours < 12 ? `AM` : `PM`
		}

		let date = `${year}-${month}-${day}`
		let time = `${hours}:${mins}:${secs}`

		// 1. 默认日期: 年-月-日
		let res = date

		// 2. 显示具体时间:时-分-秒
		if (this.displayTime) res = `${date} ${time}`

		// 3. 显示上午还是下午,星期几
		if (this.displayWeek) {
			res = `${date} ${time} ${week} 星期${weekDay[d.getDay()]}`
		}

		return res
	}
}
module.exports = {DateTime}
// exports.getTime() {}
const {DateTime} = require("./test")

// const date = new DateTime()
const date = new DateTime({
	format: true,
	displayTime: true,
	displayWeek: true,
})

console.log(date.getTime())
/**
 * 日期格式化
 */

class DateTime {
	constructor(root, {format = false, displayTime = false, displayWeek = false} = {}) {
		;(this.root = root), (this.format = format), (this.displayTime = displayTime), (this.displayWeek = displayWeek)
		this.setTime()
	}
	getTime() {
		const d = new Date()
		let [month, day, year] = [d.getMonth() + 1, d.getDate(), d.getFullYear()]
		let [hours, mins, secs] = [d.getHours(), d.getMinutes(), d.getSeconds()]
		let weekDay = ["日", "一", "二", "三", "四", "五", "六"]
		let week = ""
		// 格式化日期时间
		if (this.format) {
			month = month < 10 ? `0${month}` : month
			day = day < 10 ? `0${day}` : day
			hours = hours < 10 ? `0${hours}` : hours
			mins = mins < 10 ? `0${mins}` : mins
			secs = secs < 10 ? `0${secs}` : secs
			week = hours < 12 ? `AM` : `PM`
		}

		let date = `${year}-${month}-${day}`
		let time = `${hours}:${mins}:${secs}`

		// 1. 默认日期: 年-月-日
		let res = date

		// 2. 显示具体时间:时-分-秒
		if (this.displayTime) res = `${date} ${time}`

		// 3. 显示上午还是下午,星期几
		if (this.displayWeek) {
			res = `${date} ${time} ${week} 星期${weekDay[d.getDay()]}`
		}

		return res
		// document.querySelector(root).innerHTML = res
	}
	setTime() {
		const dateTimeLabel = document.createElement("div")
		document.body.appendChild(dateTimeLabel)
		dateTimeLabel.id = this.root

		setInterval(() => {
			dateTimeLabel.innerHTML = this.getTime()
		}, 1000)
	}
}
// module.exports = {DateTime}
export default DateTime
<script type="module" src="./MyDateTime.js"></script>
<script type="module">
    import DateTime from "./MyDateTime.js"

    const date = new DateTime("DateTime", {
        format: true,
        displayTime: true,
        displayWeek: true,
    })
</script>

数字金额格式化

示例
/**
 * author: coulsonzero
 * date: 2022-04-29
 * 功能: 数值千分位转换
 * @param {String, Number} num
 * @returns {String}
 */
function moneyFormat(num) {
	if (num == null || num == undefined || (typeof num != "number" && num != parseFloat(num))) {
		return "-"
	}
	/*
	let intStr = String(num)
		.split(".")[0]
		.replace(/(\d)(?=(?:\d{3})+$)/g, "$1,")
	let decimalStr = String((Math.round(num * 100) / 100).toFixed(2)).split(".")[1]
	return intStr + "." + decimalStr
	*/
	return parseFloat(num).toLocaleString("zh-CN", {style: "currency", currency: "CNY", minimumFractionDigits: "2"}).slice(1)
}

console.log(moneyFormat(12319018.975123))
console.log(moneyFormat("870479.8956"))
console.log(moneyFormat("870479.8751"))
console.log(moneyFormat("870479"))
console.log(moneyFormat("870479a.a123"))
console.log(moneyFormat("暂无数据"))

数组去重

export function removeDup(arr) {
	// if (arr == undefined || arr == null || arr.length == 0 || Array.isArray(arr)) return
	return arr.filter((v, i, self) => self.indexOf(v) === i)
}

币种单位换算

export function setCurrency(record) {
	const currency = {USD: "$", CNY: "¥"}
	const type = record.current_valuation_currency
	return type != undefined ? currency[type] : ""
	// return record.current_valuation_currency === 'CNY' ? '¥' : '$';
}

日期就近排序

// 如何对数组进行排序,从最早到最新?

const arr = [
	{id: 1, value: "value1", date: "2018-08-08", time: "15:27:17"},
	{id: 2, value: "value2", date: "2018-08-09", time: "12:27:17"},
	{id: 3, value: "value3", date: "2018-08-10", time: "17:27:17"},
	{id: 4, value: "value4", date: "2018-08-10", time: "01:27:17"},
]

arr.sort((a, b) => b.date.localeCompare(a.date))
console.log(arr)

Session

// 存储
window.sessionStorage[“user_name”] = "shville"
// 取
var username = window.sessionStorage.getItem(“user_name”)
// 删除
window.sessionStorage.removeItem(“user_name”)

鼠标导航栏上现下隐

document.addEventListener("mousewheel", mouseNav, false)

function mouseNav(e) {
	const header = document.getElementById("header")
	if (e.wheelDelta) {
		// if (e.wheelDelta > 0) {
		// 	console.log("鼠标向上滚动了")
		// } else {
		// 	console.log("鼠标向下滚动了")
		// }

		header.classList[e.wheelDelta > 0 ? "remove" : "add"]("header-hidden")
	}
}
.header-hidden {
	/* position: relative; */
	/* top: -100px !important; */
	opacity: 0 !important;
}

置顶按钮

const scrollToTop = document.querySelector(".scrollToTop")

window.addEventListener("scroll", () => {
	window.pageYOffset > 400 ? (scrollToTop.style.display = "block") : (scrollToTop.style.display = "none")
})
scrollToTop.addEventListener("click", () => {
	window.scroll({top: 0})
})
<section class="scrollToTop">
	<img src="./assets/logo.png" alt="" />
</section>
.scrollToTop {
	display: none;
	position: fixed;
	bottom: 5vh;
	right: 3vw;
	z-index: 1;
	background-color: black;
	padding: 1rem;
	border-radius: 3rem 3rem;
	cursor: pointer;
}

.scrollToTop img {
	height: 6vh;
}

数字递增效果

<div class="section milestones semiDark" id="milestones">
	<div class="milestones__container">
		<div class="milestone" id="ms1">
			<div class="milestone__icon">
				<img src="./assets/heart.svg" alt="" />
			</div>
			<h2 class="milestone__number">199</h2>
			<p class="milestone__info">Satisfied Customers</p>
		</div>
		<div class="milestone" id="ms2">
			<div class="milestone__icon">
				<img src="./assets/clock.svg" alt="" />
			</div>
			<h2 class="milestone__number">575</h2>
			<p class="milestone__info">Days of operations</p>
		</div>
		<div class="milestone" id="ms3">
			<div class="milestone__icon">
				<img src="./assets/check-circle.svg" alt="" />
			</div>
			<h2 class="milestone__number">49</h2>
			<p class="milestone__info">Completed Projects</p>
		</div>
		<div class="milestone" id="ms4">
			<div class="milestone__icon">
				<img src="./assets/award.svg" alt="" />
			</div>
			<h2 class="milestone__number">55</h2>
			<p class="milestone__info">Awards Won</p>
		</div>
	</div>
</div>

<script src="https://code.jquery.com/jquery-3.1.1.js"></script>

<script>
	// 数字滚动
	let isMilestonesLoaded = false
	const observer = new IntersectionObserver(
		function (entries) {
			if (entries[0].isIntersecting === true) startMilestonesCount()
		},
		{threshold: [0.5]}
	)

	observer.observe(document.querySelector(".milestones__container"))

	const startMilestonesCount = () => {
		if (!isMilestonesLoaded) {
			isMilestonesLoaded = true
			$(".milestone__number").each(function () {
				$(this)
					.prop("Counter", 0)
					.animate(
						{
							Counter: $(this).text(),
						},
						{
							duration: 4000,
							easing: "swing",
							step: function (now) {
								$(this).text(Math.ceil(now))
							},
						}
					)
			})
		}
	}
</script>

<style>
	body {
		background-color: #101010;
		font-family: "Rubik", sans-serif;
		/* overflow-x: hidden !important; */
	}

	.milestones {
		display: -ms-grid;
		display: grid;
		gap: 4vw;
	}

	.milestones .milestones__container {
		display: -ms-grid;
		display: grid;
		-ms-grid-columns: (minmax(250px, 1fr)) [auto-fit];
		grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
		gap: 5vh;
	}

	.milestones .milestones__container .milestone {
		display: -webkit-box;
		display: -ms-flexbox;
		display: flex;
		-webkit-box-align: center;
		-ms-flex-align: center;
		align-items: center;
		-webkit-box-orient: vertical;
		-webkit-box-direction: normal;
		-ms-flex-direction: column;
		flex-direction: column;
		gap: 2vh;
		background-color: black;
		padding: 2.5vw;
		border-radius: 1rem;
		-webkit-transition: 0.3s ease-in-out;
		transition: 0.3s ease-in-out;
	}

	.milestones .milestones__container .milestone:hover {
		-webkit-box-shadow: rgba(161, 12, 117, 0.534) 0px 2px 4px 0px, rgba(18, 79, 105, 0.829) 0px 2px 16px 0px;
		box-shadow: rgba(161, 12, 117, 0.534) 0px 2px 4px 0px, rgba(18, 79, 105, 0.829) 0px 2px 16px 0px;
		-webkit-transform: translateY(-10px);
		transform: translateY(-10px);
	}

	.milestones .milestones__container .milestone .milestone__icon img {
		height: 8vh;
	}

	.milestones .milestones__container .milestone .milestone__number {
		color: white;
	}

	.milestones .milestones__container .milestone .milestone__info {
		color: #3d3c3c;
	}
</style>