大狗哥传奇

  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

springboot自定义加载配置文件

发表于 2019-12-27 更新于 2020-06-30 分类于 spring

实现

springboot提供了注解@PropertySource来实现加载配置文件。 一个普通的注解配置,也可注解在其他@Configuration类上

1
2
3
4
@SpringBootApplication
@PropertySource(factory = YamlPropertySourceFactory.class, value = "classpath:application.yml")
public class Application {
}

PropertySource和PropertySourceFactory的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

package org.springframework.context.annotation;

public @interface PropertySource {
String name() default "";
String[] value();
boolean ignoreResourceNotFound() default false;
String encoding() default "";
Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;

}

public interface PropertySourceFactory {
PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException;
}

PropertySource的factory来表示使用何种PropertySourceFactory来实现加载过程,value指向的文件会被spring加载为EncodedResource实例以供PropertySourceFactory使用

value的值需要为有效资源,若我们需要加载绝对路径的资源文件,我们无视value的资源,仅加载自己所需要加载的配置文件。 首先我们了解下如何加载yaml配置

custom.yml配置文件,路径为/Users/li/java/workspace/branches/src/main/resources/custom.yml

1
foo: 1

我们可以使用FileSystemResource加载文件资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.leaderli.branches.utils;

import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.support.EncodedResource;

import java.io.File;
import java.util.Properties;

public class ConfigUtil {
public static Properties loadYamlIntoProperties(String yml) {

EncodedResource resource = new EncodedResource(new FileSystemResource(new File(yml)));
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return factory.getObject();
}
}

测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.leaderli.branches.config;

import com.leaderli.branches.utils.ConfigUtil;
import org.junit.jupiter.api.Test;

import java.util.Properties;

class YamlPropertySourceFactoryTest {
@Test
public void test() {
String yml = "/Users/li/java/workspace/branches/src/main/resources/custom.yml";
Properties properties = ConfigUtil.loadYamlIntoProperties(yml);
assert (int)properties.get("foo") == 1;
}
}

接下来只需要实现PropertySourceFactory接口即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.leaderli.branches.config;

import com.leaderli.branches.utils.ConfigUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;

import java.io.IOException;
import java.util.Properties;

public class YamlPropertySourceFactory implements PropertySourceFactory {
private static final Log LOGGER = LogFactory.getLog(YamlPropertySourceFactory.class);


@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
Properties propertiesFromYaml = ConfigUtil.loadYamlIntoProperties("/Users/li/java/workspace/branches/src/main/resources/custom.yml");
String sourceName = name != null ? name : resource.getResource().getFilename();
LOGGER.debug("resouce:" + resource);
return new PropertiesPropertySource(sourceName, propertiesFromYaml);
}

}

docker_compose

发表于 2019-12-25 更新于 2020-06-30 分类于 docker

基础

使用Dockerfile和dockder-compose.yml来定制容器

Dockerfile

1
2
FROM li:tomcat
ENTRYPOINT service ssh start && tail -f /dev/null
  1. FROM表示依赖的镜像
  2. ENTRYPOINT容器启动后执行的脚本,可用于启动ssh服务等

docker-compose.yml

使用配置文件来确定映射的镜像,参考compose-file

1
2
3
4
5
6
7
8
version: "3"
services:
web:
build: .
image: "li:linux"
ports:
- "8080:8080"
- "4022:22"
  1. services.web.image构建的镜像名称
  2. services.web.ports映射端口
  3. services.web.build构建的源文件目录

python线程

发表于 2019-12-24 更新于 2020-06-30 分类于 python

获取线程执行结果

通过继承自threading.Thread,重写其run方法来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import threading


class MyThread(threading.Thread):
def __init__(self, *args, **kwargs):
"""
使用父类构造器,尽量保持语法一致,仅将线程执行结果缓存
"""

#设定回调
if 'callable' in kwargs:
self._callable = kwargs['callable']
del kwargs['callable']
super(MyThread, self).__init__(*args, **kwargs)
self._result = None
pass

def run(self):
if self._target:
self._result = self._target(*self._args, **self._kwargs)

def result(self, _callable=None):
"""
join()确保任务已经执行完成
:return:
"""
self.join()
# 回调
if _callable is None:
_callable = self._callable
if _callable:
_callable(self._result)
return self._result

测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import threading
import time

def foo():
time.sleep(1)
return time.time()


tasks = []
for i in range(10):
tasks.append(MyThread(target=foo))
for t in tasks:
t.start()

for t in tasks:
t.result()

python队列

发表于 2019-12-24 更新于 2020-06-30 分类于 python

我们直接以代码示例来分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import queue
import threading
import time


def do_work(item, result):
time.sleep(1)
result.append(item)


def worker(_queue, _result):
while True:
item = _queue.get()
if item is None:
break
do_work(item, _result)
_queue.task_done()


def join(_queue, num):
_queue.join()
for x in range(num):
_queue.put(None)


q = queue.Queue()
results = []
threads = []
num_worker_threads = 10
for i in range(num_worker_threads):
t = threading.Thread(target=worker, args=(q, results))
t.start()
threads.append(t)

for x in range(50):
q.put(x)
print('size:', q.qsize())
join(q, num_worker_threads)
print('result:', len(results))

我们查看queue.join()的源码

1
2
3
4
5
def join(self):
with self.all_tasks_done:
#当unfinished_tasks不为0时一直等待
while self.unfinished_tasks:
self.all_tasks_done.wait()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def put(self, item, block=True, timeout=None):
with self.not_full:
if self.maxsize > 0:
if not block:
if self._qsize() >= self.maxsize:
raise Full
elif timeout is None:
while self._qsize() >= self.maxsize:
self.not_full.wait()
elif timeout < 0:
raise ValueError("'timeout' must be a non-negative number")
else:
endtime = time() + timeout
while self._qsize() >= self.maxsize:
remaining = endtime - time()
if remaining <= 0.0:
raise Full
self.not_full.wait(remaining)
self._put(item)
#put时unfinished_tasks+1
self.unfinished_tasks += 1
self.not_empty.notify()
1
2
3
4
5
6
7
8
9
def task_done(self):
with self.all_tasks_done:
#task_done时unfinished_tasks-1
unfinished = self.unfinished_tasks - 1
if unfinished <= 0:
if unfinished < 0:
raise ValueError('task_done() called too many times')
self.all_tasks_done.notify_all()
self.unfinished_tasks = unfinished

根据上述源码我们可以知道,当调用了指定次数的task_done时,join方法或重新获取到锁,从而离开阻塞状态,对于我们定义的方法join(_queue, num),中先_queue.joins,再put(None)的原因,是因为我们消费时,当item为None时直接结束线程执行,而没有再调用一次task_done。 我们可以用上述示例,来实现一个简单的线程池

python装饰器

发表于 2019-12-22 更新于 2020-06-30 分类于 python

基础

python装饰器,类似java反射,用以实现切面。 python是函数式语言,不需要使用反射,可动态的修改函数或者类。当我们需要给一段函数增加打印函数名和参数的功能时我们可以这样做

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def hello(name):
return 'hello '+name


def log(func):
def wrapper(*args, **kwargs):
print('-----------before-----------')
print(func(*args, **kwargs))
print('-----------after-----------')
return wrapper


hello = log(hello)

hello('li')

执行结果如下

-----------before----------- hello li -----------after-----------

但是当我们print(hello)时我们发现

<function log.<locals>.wrapper at 0x10db2bc80>

我们只需要在log的内部方法wrapper加上注解

1
2
3
4
5
6
7
8
9
10
11
12
import functools




def log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('-----------before-----------')
print(func(*args, **kwargs))
print('-----------after-----------')
return wrapper

这个时候print(hello)

<function hello at 0x10ef94c80>

使用注解实现装饰器

python为我们提供了语法糖,来自动实现自动给方法加上装饰器这个过程,只需要使用注解即可

完整代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

import functools


def log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('-----------before-----------')
print(func(*args, **kwargs))
print('-----------after-----------')
return wrapper


@log
def hello(name):
return 'hello '+name


hello('li')

print(hello)

输出结果如下

-----------before----------- hello li -----------after----------- <function hello at 0x102690c80>

类装饰器

python还提供了类装饰器,会自动实例化类,并在调用方法时调用类的__call__方法。 需要注意的是,在__init__方法,使用参数时,若使用位置参数,则装饰器注解不可带参数,且位置参数有且仅有一个,此参数在运行时即为被装饰的函数。若装饰器注解使用参数,则__init__方法,需使用默认参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import functools


class Log(object):

def __init__(self, func):
print('---init---')
print(inspect.getfullargspec(a_method))

self.func = func

def __call__(self, *args, **kwargs):
print('-----------before-----------')
print(self.func(*args, **kwargs))
print('-----------after-----------')


@Log
def hello(name):
return 'hello '+name


hello('li')
hello('li')

print(hello)

根据输出结果可以看到,类只加载一次,即__init__方法只会执行一次

---init--- -----------before----------- hello li -----------after----------- -----------before----------- hello li -----------after----------- <main.Log object at 0x1048ff668>

装饰器使用参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import functools


def log(* args, **kwargs):
print(args)
print(kwargs)

def decoractor(func):
def wrapper(*args, **kwargs):
print(func(*args, **kwargs))
return wrapper

return decoractor


@log('hello', 100)
def hello(name):
return 'hello '+name


@log(filter=200)
def default(name):
return 'hello '+name


hello('hello')
default('default')

类装饰器使用参数

参数需要指定默认值,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import functools


class Log(object):

def __init__(self, name='default'):
print('---init---')
self.name = name

def __call__(self, func):
def wrapper(*args, **kwargs):
print(self.name)
print(func(*args, **kwargs))
return wrapper


@Log('li')
def hello(name):
return 'hello '+name


@Log()
def default(name):
return 'hello '+name


hello('hello')
default('default')

参数与位置息息相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import functools

class Log(object):

def __init__(self, name="default", filter=0):
self.name = name
print(filter)

def __call__(self, func):
def wrapper(*args, **kwargs):
print(self.name)
print(func(*args, **kwargs))
return wrapper


@Log('hello', 100)
def hello(name):
return 'hello '+name


@Log(filter=200)
def default(name):
return 'hello '+name


hello('hello')
default('default')

jvm性能排除

发表于 2019-12-19 更新于 2020-06-30 分类于 java

查看java进程

1
jps #列出本机所有的jvm实例

部分jps参数

-m:输出主函数传入的参数. 下的 hello 就是在执行程序时从命令行输入的参数 -l: 输出应用程序主类完整 package 名称或 jar 完整名称. -v: 列出 jvm 参数, -Xms20m -Xmx50m 是启动程序指定的 jvm 参数

列出运行中的Java程序的运行环境参数

1
jinfo <pid>

打印每个class的实例数目,内存占用,类全名信息。查看是否有类异常加载

1
jmap -histo <pid>

示例 jvm性能排除_2019-12-19-23-48-31.png

可将上述命令的输出重定向到文件中,然后使用sort -n -k 2命令,根据示例数目进行排序,已确定占用数量较多的实例的类

查看当前内存使用情况

输出Java进程的堆内存信息,包括永久代、年轻代、老年代

1
jmap -heap <pid>

示例 jvm性能排除_2019-12-19-23-58-41.png

实时追踪GC情况

gcutil是按百分比占比输出gc情况的

1
jstat -gcutil <PID> 5000 # 每5秒输出一次gc

S0:  新生代中 Survivor space 0 区已使用空间的百分比 S1: 新生代中 Survivor space 1 区已使用空间的百分比 E: 新生代已使用空间的百分比 O: 老年代已使用空间的百分比 P: 永久带已使用空间的百分比 YGC: 从应用程序启动到当前,发生 Yang GC 的次数 YGCT: 从应用程序启动到当前,Yang GC 所用的时间【单位秒】 FGC: 从应用程序启动到当前,发生 Full GC 的次数 FGCT: 从应用程序启动到当前,Full GC 所用的时间 GCT: 从应用程序启动到当前,用于垃圾回收的总时间【单位秒】

示例 jvm性能排除_2019-12-20-00-04-43.png

jstack

我们已一个简单的代码来示范如何使用

1
2
3
4
5
6
7
8
public class Fooloop {
public static void main(String[] args) {
for (; ; ) {

System.out.println("args = " + args);
}
}
}

启动Fooloop后,使用top命令查看进程使用情况

进程使用,如果信息过多可考虑使用top|grep java,需要清楚哪个是cpu的占用信息,对于下述示例为9 jvm性能排除_2019-12-20-00-39-21.png

我们可以看到pid为 432 的进程cpu占用率很高,我们使用top -Hp <pid>来查看具体是哪个线程cpu占用率高。我们可以看到是433 jvm性能排除_2019-12-20-00-42-52.png

jstack命令生成的日志中,关于nid是使用16进制来表示的,而top不是。我们可以使用printf "%x\n" nid来讲10进制转换为16进制。根据此nid去jstack日志中去找到对应的信息。可使用命令jstack <pid> |grep -A 30 <nid>,

我们计算出433的十六进制为1b1 jvm性能排除_2019-12-20-00-45-36.png 通过日志,可以看出问题出在Fooloop.main(Fooloop.java:5)

jstack日志中,可以分析是否某个线程持续的输出日志,说明锁的竞争比较激烈,就有可能造成性能问题。我们也可以通过jps -v或者ps -ef|grep java等来查看具体 java 进程的pid。

jenkins

发表于 2019-12-16 更新于 2020-06-30 分类于 java

安装

使用centos 7系统安装的jenkins服务,

使用安装包进行安装。

1
2
wget https://pkg.jenkins.io/redhat/jenkins-2.213-1.1.noarch.rpm
rpm -ivh jenkins-2.213-1.1.noarch.rpm

使用yum安装

添加 Jenkins 库到 yum 库,Jenkins 将从这里下载安装。

1
2
3
wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
rpm --import https://jenkins-ci.org/redhat/jenkins-ci.org.key
yum install -y jenkins

安装过程可能出现的问题

当未卸载干净jenkins再进行重装时可能会出现的问题

1
2
3
4
5
6
7
8
9
stat: cannot stat ‘/var/cache/jenkins’: No such file or directory
stat: cannot stat ‘/var/log/jenkins’: No such file or directory
stat: cannot stat ‘/var/lib/jenkins’: No such file or directory
error: %pre(jenkins-2.150.1-1.1.noarch) scriptlet failed, exit status 1
Error in PREIN scriptlet in rpm package jenkins-2.150.1-1.1.noarch
Verifying : jenkins-2.150.1-1.1.noarch 1/1

Failed:
jenkins.noarch 0:2.150.1-1.1

问题的关键是PREIN scriptlet,就是preinstall scriptlet,这是rpm在安装前执行的一段sh脚本,为安装创建相应的文件夹什么的。

上面的三个No such file or directory显然就是,这三个文件夹没有被创建好。

1
2
yum install jenkins  --downloadonly --downloaddir=/root
rpm --scripts -qp jenkins-2.150.1-1.1.noarch.rpm > jenkins.log

查看输出的日志

1
2
3
4
5
6
7
8
9
if [ -f "/etc/sysconfig/jenkins" ]; then
logger -t jenkins.installer "Found previous config file /etc/sysconfig/jenkins"
. "/etc/sysconfig/jenkins"
stat --format=%U "/var/cache/jenkins" > "/tmp/jenkins.installer.cacheowner"
stat --format=%U "/var/log/jenkins" > "/tmp/jenkins.installer.logowner"
stat --format=%U ${JENKINS_HOME:-/var/lib/jenkins} > "/tmp/jenkins.installer.workdirowner"
else
logger -t jenkins.installer "No previous config file /etc/sysconfig/jenkins found"
fi

这问题就很明白了,结合前面的stat报错,明确了就是这段报错误。这段的意思是,如果/etc/sysconfig/jenkins存在,执行下面一系列操作,应该是为了重复安装写的。

删除文件夹/var/lib/jenkins,/var/log/jenkins/,/var/cache/jenkins即可

启动

jenkins启动时会输出日志,可通过查看日志定位具体错误信息。

1
tail -f /var/log/jenkins/jenkins.log

使用rpm安装jenkins时,默认会创建jenkins:jenkins的用户以及用户组,如果用其他用户启动,需要将以下文件夹用户以及用户组变更

  1. /usr/lib/jenkins/jenkins.war WAR 包
  2. /etc/sysconfig/jenkins 配置文件
  3. /var/lib/jenkins/ 默认的 JENKINS_HOME 目录
  4. /var/log/jenkins/jenkins.log Jenkins 日志文件

启停服务,使用systemctl工具操作jenkins

1
2
3
systemctl start jenkins
systemctl stop jenkins
systemctl status jenkins

修改端口

vi /etc/sysconfig/jenkins修改JENKINS_PORT="8081"

防火墙

当虚拟机外部需要访问jenkins时,需要将centos的防火墙关闭

  1. 使用命令systemctl status firewalld.service查看防火墙状态
  2. 使用命令systemctl disable firewalld.service,即可永久禁止防火墙服务

excel

发表于 2019-12-06 更新于 2020-06-30 分类于 tips

折线图

excel_2019-12-06-01-21-28.png excel 折线图的单元格需要为数字,否则无法成功转换。有时候需要将走上角第一个空出来。可以通过更改数据类型进行 x,y 轴反转

js-tips

发表于 2019-12-05 更新于 2020-12-03 分类于 js

格式化输出 json

1
2
var json = JSON.stringify(jsonObj, null, 4);
consol.log(json);

js 可变参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var a = 100;
var b = 100;

function test() {
var obj = {};

for (var i = 0; i < arguments.length; i++) {
var name = arguments[i];
obj[name] = eval(name);
}

return obj;
}
console.log(test("a", "b"));

可以直接使用for of语法遍历元素

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = 100;
var b = 100;

function test() {
var obj = {};

for (let value of arguments) {
console.log(value);
}

return obj;
}
test("a", "b");

es6 标准还提供了...表示可变参数,该参数只能为最后一个参数

1
2
3
4
5
6
7
8
function test(name, ...args) {
console.log(name);
for (let arg of args) {
console.log(arg);
}
}

test("li", 100, 200, 300);

根据空行分割字符串

1
console.log(str.trim().split(/\s+/));

js 进制

1
2
3
4
5
6
//二进制0b开头
//八进制0开头
//十六进制0x开头
var a = 0b10;
var b = 070;
var c = 0x36;

属性依赖赋值

js 对象的属性值延迟加载

1
2
3
4
5
6
7
8
9
10
app = {};
Object.defineProperty(app, "config", {
get: (function () {
var inner = {};
inner.get = function () {
return inner.config || (inner.config = 1234);
};
return inner.get;
})(),
});

console

1
2
3
4
5
6
7
8
9
10
11
12
var obj = [
{
name: 1,
age: 1,
},
{
name: 2,
age: 2,
},
];

console.table(obj);
js-tips_table.png
js-tips_table.png

object

查看 obj 的所有 key 以及 values

1
2
Object.keys(obj);
Object.values(obj);

实现 obj 的 clone

1
2
3
4
5
//浅clone
Object.assign({}, obj);

//深clone
JSON.parse(JSON.stringify(obj));

递归 dom

1
2
3
4
5
6
7
8
9
var els = document.getElementsByClassName("myclass");

Array.prototype.forEach.call(els, function(el) {
// Do stuff here
console.log(el.tagName);
});

// Or
[].forEach.call(els, function (el) {...});

递归检测属性是否存在

查看 obj 内的 obj 的属性的值,避免 undefined 异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var object = {
innerObject: {
deepObject: {
value: "Here am I",
},
},
};

if (
object &&
object.innerObject &&
object.innerObject.deepObject &&
object.innerObject.deepObject.value
) {
console.log("We found it!");
}

//根据这个特性,我们可以写出如下的用法

var msg = (object && object.innerObject) || "hello";

replaceALl

js 原生没有 replaceAll 方法,可以通过

1
2
str = "Test abc test test abc test...".split("abc").join("");
str.split(search).join(replacement);

事件冒泡

阻止事件冒泡

  1. 给子级加 event.stopPropagation( )

    1
    2
    3
    4
    $("#div1").mousedown(function (e) {
    var e = event || window.event;
    event.stopPropagation();
    });
  2. 在事件处理函数中返回 false

    1
    2
    3
    4
    $("#div1").mousedown(function (event) {
    var e = e || window.event;
    return false;
    });
  3. event.target==event.currentTarget,让触发事件的元素等于绑定事件的元素,也可以阻止事件冒泡;

    1
    2
    3
    4
    5
    $("#div1").mousedown(function (event) {
    if (event.target == event.currentTarget) {
    console.log(event);
    }
    });

触发事件与自定义事件

1
2
3
4
5
6
7
8
9
10
11
12
13
const event = new Event("build");

// Listen for the event.
elem.addEventListener(
"build",
function (e) {
/* ... */
},
false
);

// Dispatch the event.
elem.dispatchEvent(event);

数组相关

返回指定长度的数组,类似 java 流的 limit

1
2
3
4
5
6
7
8
9
10
11
12
13
//从0角标最多截取5位
arr.slice(0, 5);
//截取到最后一位前
arr.splice(0, -1);

//截取到角标5
arr.slice(5);

//可实现clone
arr.slice();

//从0角标移除1位,返回被删除的元素
arr.splice(0, 1);

URL 参数

对于有#锚点的 URL 处理,下述方法是无法使用的

1
2
3
4
var url_string = "http://www.example.com/t.html?a=1&b=3&c=m2-m3-m4-m5"; //window.location.href
var url = new URL(url_string);
var c = url.searchParams.get("c");
console.log(c);

判断元素是否包含

1
2
3
4
5
6
7
8
<div id="parent">
<div id="children"></div>
</div>
<script>
var A = document.getElementById("parent");
var B = document.getElementById("children");
console.log(A.contains(B)); //true
</script>

js 方法参数指定默认值

1
2
3
4
5
6
function func(n = 100) {
console.log(n);
}

func(); //100
func(5); //5

console 格式

%c标记 css 样式的位置

1
2
3
4
5
6
console.log("%c Oh my heavens! ", "background: #222; color: #bada55");
console.log(
"%c Oh my %c heavens! ",
"background: #222; color: #bada55",
"color: #bada55"
);

变量占位符,使用`包裹字符串

1
2
3
4
var a = { name: "li" };

// 占位符也可以用来直接打印处二进制bytes数组
console.log(person:`${a}`);

cookik

1
2
3
4
//cookie只会新增或覆盖指定key
document.cookie = "a=1"; //a=1
document.cookie = "b=1"; //a=1;b=1
document.cookie = "a=2"; //a=2;b=1

下载文件

1
2
3
4
var link = document.createElement("a");
link.download = "filename";
link.href = "http://example.com/files";
link.click();

统计执行时间

1
2
console.time();
console.timeEnd();

获取零点时间

1
2
3
4
5
var d = new Date();
//当天零点的时间戳 00:00:00
d.setHours(0, 0, 0, 0);
//当天午夜的时间戳 23:59:59
d.setHours(24, 0, 0, 0);

正则

匹配组(仅匹配到第一个满足的)

1
2
3
4
5
6
let str = "<h1>Hello, world!</h1>";

let tag = str.match(/<(.*?)>/);

alert(tag[0]); // <h1>
alert(tag[1]); // h1

多个匹配组 js-tips_多层级.png

1
2
3
4
5
6
7
8
9
let str = '<span class="my">';

let regexp = /<(([a-z]+)\s*([^>]*))>/;

let result = str.match(regexp);
alert(result[0]); // <span class="my">
alert(result[1]); // span class="my"
alert(result[2]); // span
alert(result[3]); // class="my"

匹配所有满足的组

1
2
3
4
5
6
7
8
9
10
11
let results = "<h1> <h2>".matchAll(/<(.*?)>/gi);

// results - is not an array, but an iterable object
alert(results); // [object RegExp String Iterator]

alert(results[0]); // undefined (*)

results = Array.from(results); // let's turn it into array

alert(results[0]); // <h1>,h1 (1st tag)
alert(results[1]); // <h2>,h2 (2nd tag)

css

发表于 2019-12-03 更新于 2020-12-02 分类于 frontend

DOM

一个 DOM 有一个树形结构,标记语言中的每一个元素、属性以及每一段文字都对应着结构树中的一个节点(Node/DOM 或 DOM node)。节点由节点本身和其他 DOM 节点的关系定义,有些节点有父节点,有些节点有兄弟节点(同级节点)。

CSS 究竟是怎么工作的

  1. 浏览器载入 HTML 文件(比如从网络上获取)。
  2. 将 HTML 文件转化成一个 DOM(Document Object Model),DOM 是文件在计算机内存中的表现形式,下一节将更加详细的解释 DOM。
  3. 接下来,浏览器会拉取该 HTML 相关的大部分资源,比如嵌入到页面的图片、视频和 CSS 样式。JavaScript 则会稍后进行处理,简单起见,同时此节主讲 CSS,所以这里对如何加载 JavaScript 不会展开叙述。
  4. 浏览器拉取到 CSS 之后会进行解析,根据选择器的不同类型(比如 element、class、id 等等)把他们分到不同的“桶”中。浏览器基于它找到的不同的选择器,将不同的规则(基于选择器的规则,如元素选择器、类选择器、id 选择器等)应用在对应的 DOM 的节点中,并添加节点依赖的样式(这个中间步骤称为渲染树)。
  5. 上述的规则应用于渲染树之后,渲染树会依照应该出现的结构进行布局。
  6. 网页展示在屏幕上(这一步被称为着色)。

结合下面的图示更形象: css_css渲染原理.png

如何使用 CSS

  1. 外部样式,通过 link 引用

    1
    <link rel="stylesheet" href="http://example/styles.css" />

    @import可在 css 样式中引入其他 css 样式

  2. 内部样式,直接书写在 head 的 style 中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <html>
    <head>
    <style>
    p {
    color: red;
    }
    </style>
    </head>
    <body>
    <p>This is my first CSS example</p>
    </body>
    </html>
  3. 内联样式,声明在 html 节点上

    1
    2
    3
    <h1 style="color: blue;background-color: yellow;border: 1px solid black;">
    Hello World!
    </h1>

选择器

  1. 类型选择器,根据节点的类型,即节点标签名

    1
    2
    3
    p {
    color: red;
    }
  2. 多个选择器,可同时指定多个节点的类型

    1
    2
    3
    4
    5
    h1,
    h2,
    div {
    color: red;
    }
  3. 后代选择器,仅处理某个类型的节点的所有子节点,包括子节点的子节点

    1
    2
    3
    div p {
    color: red;
    }
  4. 子选择器,仅处理某个类型的节点的直接子节点

    1
    2
    3
    div > p {
    color: red;
    }
  5. 同级选择器,仅处理某个类型的节点同级的节点

    1
    2
    3
    div ~ p {
    color: red;
    }
  6. 毗邻选择器,仅处理某个类型的节点同级的紧连着的下一个且为指定类型的节点

    1
    2
    3
    div + p {
    color: red;
    }
  7. 类选择器,节点上的 class 属性

    1
    2
    3
    .testClass {
    color: red;
    }
  8. ID 选择器,节点上的 id 属性

    1
    2
    3
    #testId {
    color: red;
    }
  9. 伪类选择器,节点的特定状态下

    1
    2
    3
    a:hover {
    color: red;
    }

    伪类状态有如下

    • :active 向被激活的元素添加样式。
    • :focus 向拥有键盘输入焦点的元素添加样式。
    • :hover 当鼠标悬浮在元素上方时,向元素添加样式。
    • :link向未被访问的链接添加样式。
    • :visited 向已被访问的链接添加样式。
    • :first-child 向元素的第一个子元素添加样式。
    • :lang向带有指定 lang 属性的元素添加样式。
    • nth-of-type(2)相对于父类元素的第几个该标签
  10. 属性选择器,选择具有某属性的节点

    1
    2
    3
    4
    5
    6
    [src] {
    color: red;
    }
    a[src='hello'] {
    color: red;
    }

    属性值可以使用通配符

    1
    2
    3
    4
    5
    6
    /*表示以http开头*/
    [src~='http']
    /*表示以html结尾*/
    [src$='.html']
    /*表示包含test*/
    [src*='test']
  11. 全局选择器,选择所有

    1
    2
    3
    * {
    color: red;
    }

css 加载顺序

css 根据选择器的优先级呈现一种瀑布流层级重叠关系,优先级越高的属性越先加载,当选择器没有相关属性的定义时,会去向第一层优先级的选择器中去找直到找到。选择器的加载顺序遵循一个选择器描述的越具体,其加载顺序越高。

  1. !important修饰的属性优先级最高
  2. 内联样式表即标签上的style的值
  3. ID 选择器
  4. class 类选择器的权值
  5. 属性选择器
  6. 节点类型选择器

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<style>
.c1 {
color: green;
}

.c2 {
color: black;
}

.c1 .c2 {
color: red;
}

.c3 {
color: violet !important;
}

#id {
color: gold;
font-size: xx-large;
}

[title] {
color: green;
font-size: xx-large;
}
</style>
</head>

<body>
<div class="c1">c1</div>
<div class="c2">c2</div>
<div class="c1 c2">c1 and c2</div>
<div class="c1">
<div class="c2">c2 in c1</div>
</div>
<div class="c2" title="">c2 with title</div>
<div class="c2" id="id">c2 with id</div>
<div class="c2" style="color: yellowgreen;">inline style in c2</div>
<div class="c3" style="color: yellowgreen;">inline style in c3</div>
</body>
</html>

显示效果 css_显示效果.png

继承

一些设置在父元素上的 css 属性是可以被子元素继承的,有些则不能。
CSS 能自动继承的属性

  • border-collapse
  • border-spacing
  • color
  • cursor
  • direction
  • empty-cells
  • font(-style | - variant | -weight | -size | -family |-stretch | -size | -adjust)
  • letter-spacing
  • line-height
  • list-style(-image | -position | -type)
  • overflow-wrap
  • pointer-events
  • quotes
  • text-indent 缩进
  • text-size-adjust
  • text-underline-position
  • text-shadow
  • text-align
  • text-align-last
  • text-justify
  • test-transform
  • tab-size
  • table-layout
  • white-space
  • word-break
  • word-wrap
  • word-spacing
  • writing-mode
  • visibility
  • zoom

CSS 为控制继承提供了四个特殊的通用属性值。每个 css 属性都接收这些值。

  1. inherit 设置该属性会使子元素属性和父元素相同。实际上,就是 "开启继承".
  2. initial 设置属性值和浏览器默认样式相同。如果浏览器默认样式中未设置且该属性是自然继承的,那么会设置为 inherit 。
  3. unset 将属性重置为自然值,也就是如果属性是自然继承那么就是 inherit,否则和 initial 一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<html>
<style>
body {
color: green;
}
.inherit a {
color: inherit;
}
.initial a {
color: initial;
}
.unset a {
color: unset;
}
</style>
<body>
<div>link:<a href="#">default</a></div>
<div class="inherit">link:<a href="#">inherit</a></div>
<div class="initial">link: <a href="#">initial</a></div>
<div class="unset">link:<a href="#">unset</a></div>
</body>
</html>

显示效果如下

css_css_control.png
css_css_control.png

属性和值

一对属性和值组成一个 css 的声明,属性的值可以使用方法

  1. calc()

    1
    2
    3
    4
    5
    6
    7
    .box {
    margin: 30px;
    width: 100px;
    height: 100px;
    background-color: rebeccapurple;
    transform: rotate(0.8turn);
    }
    1
    <div class="box"></div>
    css_css.png
    css_css.png
  2. rotate()

    1
    2
    3
    4
    5
    6
    7
    .box {
    margin: 30px;
    width: 100px;
    height: 100px;
    background-color: rebeccapurple;
    transform: rotate(0.8turn);
    }
    1
    <div class="box"></div>
    css_rotate.png
    css_rotate.png

定位

position 属性用于指定一个元素在文档中的定位方式。top,right,bottom 和 left 属性则决定了该元素的最终位置。

  1. static 默认值,static 定位所导致的元素位置,是浏览器自主决定的,所以这时 top、bottom、left、right 这四个属性无效。
  2. relative relative 表示,相对于默认位置(即 static 时的位置)进行偏移,即定位基点是元素的默认位置
  3. absolute 相对于上级元素(一般是父元素)进行偏移,即定位基点是父元素。它有一个重要的限制条件:定位基点(一般是父元素)不能是 static 定位,否则定位基点就会变成整个网页的根元素 html。absolute不会占据当前的位置,当未设定 top,right,bottom 和 left 时会处于当前的位置,但不会排挤其他元素
  4. fixed 表示,相对于视口(viewport,浏览器窗口)进行偏移,即定位基点是浏览器窗口。这会导致元素的位置不随页面滚动而变化,好像固定在网页上一样。需要设置 top,right,bottom 和 left 至少一个参数
  5. sticky sticky 跟前面四个属性值都不一样,它会产生动态效果,很像 relative 和 fixed 的结合:一些时候是 relative 定位(定位基点是自身默认位置),另一些时候自动变成 fixed 定位(定位基点是视口)。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<html>
<style>
body {
margin: 8px;
padding: 0px;
}
div {
width: 100px;
height: 100px;
}
.static {
border: 1px solid orange;
color: orange;
}
.relative {
border: 1px solid green;
border-color: green;
position: relative;
top: 10px;
}
.absolute {
position: absolute;
border: 1px solid paleturquoise;
color: paleturquoise;
}
.absolute_top {
position: absolute;
border: 2px solid pink;
color: pink;
top: 20px;
}
.absolute_relative {
position: absolute;
border: 2px solid blueviolet;
color: blueviolet;
}
.absolute_relative_top {
position: absolute;
top: 30px;
border: 2px solid red;
color: red;
}
</style>
<body>
<div class="static">static_1</div>
<div class="relative">relative_top_10_2</div>
<div class="static">static_3</div>
<div class="absolute">____________________absolute_4</div>
<div class="absolute_top">____________________absolute_5</div>
<div class="static">static_6</div>
<div style="position:relative">
<div class="absolute_relative">
____________________absolute_relative_7
</div>
</div>
<div style="position:relative">
<div class="absolute_relative_top">
____________________absolute_relative_top_30_8
</div>
</div>
</body>
</html>

效果如下 css_absolute.png

sticky 的示例,当 relative 的元素任意部分处于相对于视口top=50px的位置时,sticky 的元素则处于 fixed 的状态。否则,和 relative 一起移动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<html>
<style>
body {
margin: 8px;
}

div {
width: 400px;
height: 400px;
border: 1px solid green;
border-top-color: red;
}

.relative {
border: 1px solid green;
border-color: green;
position: relative;
top: 10px;
}

.sticky {
background: green;
top: 50px;
position: sticky;
width: 600px;
height: 10px;
}
</style>

<body>
<div></div>
<div class="relative">
<div class="sticky"></div>
</div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</body>
</html>

叠放

z-index元素的叠放顺序,值越大越在上面

盒子模型

所谓盒子模型(Box Model)就是把 HTML 页面中的元素看作是一个矩形的盒子,也就是一个盛装内容的容器。所有的文档元素(标签)都会生成一个矩形框,我们成为元素框(element box),它描述了一个文档元素再网页布局汇总所占的位置大小。因此,每个盒子除了有自己大小和位置外,还影响着其他盒子的大小和位置。 css_标准盒子模型.png

CSS 中组成一个块级盒子需要:

  • Content box: 这个区域是用来显示内容,大小可以通过设置 width 和 height.
  • Padding box: 包围在内容区域外部的空白区域; 大小通过 padding 相关属性设置。
  • Border box: 边框盒包裹内容和内边距。大小通过 border 相关属性设置。
  • Margin box: 这是最外面的区域,是盒子和其他元素之间的空白区域。大小通过 margin 相关属性设置。

在 chrome 中当选中某个节点时,chrome dev 控制台会显示其盒子模型,且其盒子模型的中各个组件的颜色会体现在你选中的节点上

控制台的盒子模型的数值是可以编辑的 css_盒子模型.png

布局

display:[type]

  1. block

    • 盒子会在内联的方向上扩展并占据父容器在该方向上的所有可用空间,在绝大数情况下意味着盒子会和父容器一样宽
    • 每个盒子都会换行
    • width 和 height 属性可以发挥作用
    • 内边距(padding), 外边距(margin) 和 边框(border) 会将其他元素从当前盒子周围“推开”

    默认为 block 的标签

    • <h1> ~ <h6>
    • <p>
    • <div>
  2. inline

    • width 和 height 属性将不起作用。
    • 垂直方向的内边距、外边距以及边框会被应用但是不会把其他处于 inline 状态的盒子推开。
    • 水平方向的内边距、外边距以及边框会被应用而且也会把其他处于 inline 状态的盒子推开。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <html>
    <style>
    body {
    margin: 8px;
    }

    div {
    display: inline;
    width: 100px;
    height: 80px;
    border: 1px solid green;
    border-top-color: red;
    margin: 10px;
    }
    </style>

    <body>
    <div>1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    <div>5</div>
    <div>6</div>
    </body>
    </html>

    显示效果如下 css_inline.png 当display:block时 css_block.png

    默认为 inline 的标签

    • <a>
    • <span>
    • <em>
    • <strong>
  3. flex 我们可以通过使用类似 flex 的 display 属性值来更改内部显示类型。 如果设置 display: flex,在一个元素上,外部显示类型是 block,但是内部显示类型修改为 flex。 该盒子的所有直接子元素都会成为 flex 元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    <style>
    p,
    ul {
    border: 2px solid rebeccapurple;
    padding: 0.5em;
    }

    .block,
    li {
    border: 2px solid blue;
    padding: 0.5em;
    }

    ul {
    display: flex;
    list-style: none;
    }

    .block {
    display: block;
    }
    </style>
    <p>I am a paragraph. A short one.</p>
    <ul>
    <li>Item One</li>
    <li>Item Two</li>
    <li>Item Three</li>
    </ul>
    <p>
    I am another paragraph. Some of the <span class="block">words</span> have
    been wrapped in a <span>span element</span>.
    </p>
  4. inline-block 它在内联和块之间提供了一个中间状态,实现我们需要的块级的部分效果:

    • 不会跳转到新行,如果显式添加 width 和 height 属性,它只会变得比其内容更大。
    • 设置 width 和 height 属性会生效。
    • padding, margin, 以及 border 会推开其他元素。

    inline 中的示例,当display:inline-block时显示效果如下 css_inline-block.png

flex 盒子

  1. flex 模型当元素表现为 flex 框时,它们沿着两个轴来布局: css_flex模型.png

    • 主轴(main axis)是沿着 flex 元素放置的方向延伸的轴(比如页面上的横向的行、纵向的列)。该轴的开始和结束被称为 main start 和 main end。
    • 交叉轴(cross axis)是垂直于 flex 元素放置方向的轴。该轴的开始和结束被称为 cross start 和 cross end。
    • 设置了 display: flex 的父元素被称之为 flex 容器(flex container)。
    • 在 flex 容器中表现为柔性的盒子的元素被称之为 flex 项(flex item)
  2. 设定主轴方向,默认值是row

    1
    flex-direction: column;

    你还可以使用 row-reverse 和 column-reverse 值反向排列 flex 项目。用这些值试试看吧!

  3. 当你在布局中使用定宽或者定高的时候,可能会出现问题即处于容器中的 弹性盒子子元素会溢出,破坏了布局。 可在 flex 容器中设定

    1
    flex-wrap: wrap;

    在 flex 项中设定宽度

    1
    flex: 200px;

    flex-direction和flex-wrap可以缩写为flex-flow,例如flex-flow:row wrap

  4. 控制 flex 项占用空间的比例

    • flex-basis 指定 flex 元素在主轴上占用 flex 容器的尺寸,默认为 auto,即以 flex 元素的尺寸作为其占用的尺寸。
    • flex-grow 指定各个元素的弹性增长因数,默认为 0
    • flex-shrink 指定各个元素的弹性收缩因数,默认为 0,元素不能无限缩写,不能小于min-width和min-height
    • flex flex-grow flex-shrink flex-basic 的缩写 例如:这表示“每个 flex 项将首先给出 200px 的可用空间,然后,剩余的可用空间将根据分配的比例共享
    1
    2
    3
    4
    5
    6
    7
    article {
    flex: 1 200px;
    }

    article:nth-of-type(3) {
    flex: 2 200px;
    }
  5. flex 项排序

    • 所有 flex 项默认的 order 值是 0。
    • order 值大的 flex 项比 order 值小的在显示顺序中更靠后。
    • 相同 order 值的 flex 项按源顺序显示。所以假如你有四个元素,其 order 值分别是 2,1,1 和 0,那么它们的显示顺序就分别是第四,第二,第三,和第一
    1
    2
    3
    button:first-child {
    order: 1;
    }
  6. flex 嵌套 弹性盒子也能创建一些颇为复杂的布局。设置一个元素为 flex 项目,那么他同样成为一个 flex 容器,它的孩子(直接子节点)也表现为 flexible box

  7. flex 相关的一些 css

    • align-items 纵向对齐方式,例如align-items:center表现为纵向居中对齐
    • justify-content 横向对齐方式,例如justify-items:center表现为横向居中对齐

外边距折叠

块的上外边距(margin-top)和下外边距(margin-bottom)有时合并(折叠)为单个边距,其大小为单个边距的最大值(或如果它们相等,则仅为其中一个),这种行为称为边距折叠。

  1. 同一层相邻元素之间 相邻的两个元素之间的外边距重叠,除非后一个元素加上 clear-fix 清除浮动。
  2. 空的块级元素 当一个块元素上边界 margin-top 直接贴到元素下边界 margin-bottom 时也会发生边界折叠。这种情况会发生在一个块元素完全没有设定边框 border、内边距 paddng、高度 height、最小高度 min-height 、最大高度 max-height 、内容设定为 inline 或是加上 clear-fix 的时候。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <style>
    ​​​​​​p {
    margin: 0;
    }
    div {
    margin-top: 13px;
    margin-bottom: 87px;
    }
    </style>

    <p>上边界范围是 87 ...</p>
    <div></div>
    <p>... 上边界范围是 87</p>
  3. 没有内容将父元素和后代元素分开 如果没有边框 border,内边距 padding,行内内容,也没有创建块级格式上下文或清除浮动来分开一个块级元素的上边界 margin-top 与其内一个或多个后代块级元素的上边界 margin-top;或没有边框,内边距,行内内容,高度 height,最小高度 min-height 或 最大高度 max-height 来分开一个块级元素的下边界 margin-bottom 与其内的一个或多个后代后代块元素的下边界 margin-bottom,则就会出现父块元素和其内后代块元素外边界重叠,重叠部分最终会溢出到父级块元素外面。

浮动

1
2
3
4
img {
float: left;
margin-right: 30px;
}

浮动元素会脱离正常的文档布局流,并吸附到其父容器的左边 。在正常布局中位于该浮动元素之下的内容,此时会围绕着浮动元素,填满其右侧的空间。 css_float.png

现在你已经知道了关于 float 属性的一些有趣事实,不过你很快就能够碰到一个问题——所有在浮动下面的自身不浮动的内容都将围绕浮动元素进行包装。

有一种简单的方法可以解决这个问题—— clear 属性。当你把这个应用到一个元素上时,它主要意味着"此处停止浮动"——这个元素和源码中后面的元素将不浮动

1
2
3
footer {
clear: both;
}

- left:停止任何活动的左浮动 - right:停止任何活动的右浮动 - both:停止任何活动的左右浮动

CSS 多行文本溢出省略显示

1
2
3
4
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; #省略显示
# white-space: wrap; #换行显示
1
2
3
4
5
6
7
8
9
10
11
<style>
div {
overflow: hidden;
text-overflow: ellipsis;
border: solid 1px orange;
width: 100px;
}
</style>
<div>
<span class="h">hello hello hello1234567889</span>
</div>
css_浮动.png
css_浮动.png

多个class生效问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<link rel="stylesheet" type="text/css" href="c1.css" />
<link rel="stylesheet" type="text/css" href="c2.css" />
<link rel="stylesheet" type="text/css" href="c3.css" />
</head>
<body>
<div class="c1 c2">c1 c2</div>
<div class="c2 c1">c2 c1</div>
<div class="c2 c3">c2 c3</div>
<div class="c3 c2">c3 c2</div>
</body>
</html>

c1.css

1
2
3
.c1 {
color: blue;
}

c2.css

1
2
3
.c2 {
color: green;
}

c3.css

1
2
3
.c3 {
color: red;
}

显示效果

css_2019-12-04-00-38-34.png 当一个div指定多个class时,和指定css样式的顺序无关,只和加载css的顺序有关

各种示例

图片和文字水平居中对齐

1
2
3
4
5
6
7
8
9
10
<div>
<img src="https://placehold.it/60x60" />
<span style="">Works.</span>
</div>
<style>
span {
vertical-aligin: top;
line-height: 50px;
}
</style>

两个元素一行居中对齐 css_aligin-items.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<style>
.parent {
display: flex;
float: left;
align-items: center;
/* justify-content: center; 横向居中*/
border: 1px solid green;
width: 100%;
}
</style>

<div class="parent">
<audio src="#" controls></audio>
aaa
</div>
<div class="parent">
<audio src="#" controls></audio>
aaa
</div>

css 属性解释

list-style

<ul>下的<li>列的头的标记

1…8910…15

lijun

与其感慨路难行,不如马上就出发
145 日志
30 分类
117 标签
© 2019 – 2020 lijun