Merge branch 'master' into backup
@ -7,6 +7,7 @@
|
||||
"clean": "hexo clean",
|
||||
"deploy": "hexo deploy",
|
||||
"server": "hexo server",
|
||||
"serve": "hexo cl && hexo s",
|
||||
"dev": "hexo cl && hexo g && gulp",
|
||||
"go": "hexo cl && hexo g && gulp && hexo d",
|
||||
"cz": "cz",
|
||||
|
@ -1,31 +0,0 @@
|
||||
pipeline {
|
||||
agent any
|
||||
stages {
|
||||
stage('检出') {
|
||||
steps {
|
||||
checkout([
|
||||
$class: 'GitSCM',
|
||||
branches: [[name: env.GIT_BUILD_REF]],
|
||||
userRemoteConfigs: [[
|
||||
url: env.GIT_REPO_URL,
|
||||
credentialsId: env.CREDENTIALS_ID
|
||||
]]])
|
||||
}
|
||||
}
|
||||
stage('自定义构建过程') {
|
||||
steps {
|
||||
echo '自定义构建过程开始'
|
||||
}
|
||||
}
|
||||
stage('同步至Github') {
|
||||
steps {
|
||||
echo '-- Start push --'
|
||||
sh 'ls'
|
||||
sh 'git remote set-url origin https://DefectingCat:$ID@github.com/DefectingCat/DefectingCat.github.io.git'
|
||||
sh 'git remote -v'
|
||||
sh 'git push origin HEAD:backup'
|
||||
echo '-- All Done --'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
pipeline {
|
||||
agent any
|
||||
stages {
|
||||
stage('检出') {
|
||||
steps {
|
||||
checkout([
|
||||
$class: 'GitSCM',
|
||||
branches: [[name: env.GIT_BUILD_REF]],
|
||||
userRemoteConfigs: [[
|
||||
url: env.GIT_REPO_URL,
|
||||
credentialsId: env.CREDENTIALS_ID
|
||||
]]])
|
||||
}
|
||||
}
|
||||
stage('构建') {
|
||||
steps {
|
||||
echo '-- 构建过程开始 --'
|
||||
sh 'npm -v'
|
||||
sh 'apt install nasm -y'
|
||||
sh 'npm install hexo -g'
|
||||
sh 'npm install'
|
||||
echo '-- hexo环境构建完成 --'
|
||||
}
|
||||
}
|
||||
stage('编译') {
|
||||
steps {
|
||||
echo '-- 开始生成文章 --'
|
||||
sh 'hexo cl'
|
||||
sh 'hexo g'
|
||||
echo '-- 文章生成结束 --'
|
||||
}
|
||||
}
|
||||
stage('同步至Github') {
|
||||
steps {
|
||||
echo '-- Push to Github --'
|
||||
sh 'ls'
|
||||
sh 'git remote set-url origin https://DefectingCat:$ID@github.com/DefectingCat/DefectingCat.github.io.git'
|
||||
sh 'git remote -v'
|
||||
sh 'git push origin HEAD:file'
|
||||
echo '-- All Done --'
|
||||
}
|
||||
}
|
||||
stage('部署至云存储') {
|
||||
steps {
|
||||
echo '-- 开始部署至云存储 --'
|
||||
sh "coscmd config -a ${env.COS_SECRET_ID} -s ${env.COS_SECRET_KEY} -b ${env.COS_BUCKET_NAME} -r ${env.COS_BUCKET_REGION}"
|
||||
sh 'rm -rf .git'
|
||||
sh 'coscmd upload -r ./public/ /'
|
||||
echo '-- 部署完成 --'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
## 自定义配置项备份
|
||||
|
||||
`custom.styl`:自定义css,`source/css/_custom`
|
||||
|
||||
`tranquil-heart.min.css`:高亮css,`source/lib/prettify`
|
||||
|
||||
`zh-Hans.yml`:`languages`
|
@ -1,95 +0,0 @@
|
||||
// Custom styles
|
||||
//图片
|
||||
img
|
||||
border-radius 0.8rem
|
||||
transition all .5s
|
||||
-webkit-transition all .5
|
||||
-ms-transition all .5
|
||||
img:hover
|
||||
transition: all 0.5s
|
||||
-webkit-transition: all 0.5s
|
||||
-ms-transition: all 0.5s
|
||||
transform: scale(1.05)
|
||||
|
||||
/*ul and ol*/
|
||||
.markdown-body a
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
color: #00f4e8;
|
||||
transition: all .12s;
|
||||
|
||||
.markdown-body li:hover
|
||||
text-shadow: 3px 3px 2px #2f2f2f57;
|
||||
|
||||
/*ol使用css排序进行定义序号*/
|
||||
.markdown-body ol
|
||||
counter-reset: xxx 0 !important;
|
||||
|
||||
.markdown-body ol li:before
|
||||
content: counter(xxx,decimal) "." !important;
|
||||
counter-increment: xxx 1 !important;
|
||||
position:absolute;
|
||||
font-family:'Comic Sans MS','Open Sans','Microsoft Yahei','Microsoft Yahei',-apple-system,sans-serif !important;
|
||||
color:#000;
|
||||
top:0;
|
||||
left:0;
|
||||
text-align:center;
|
||||
font-size:1.2em;
|
||||
opacity:.5;
|
||||
/*使用行高来调整前缀的上下位置*/
|
||||
line-height:1.33;
|
||||
text-shadow:4px 4px 1px rgba(0,0,0,.1);
|
||||
-webkit-transition:.5s;transition:.5s
|
||||
|
||||
.markdown-body ol li:hover:before
|
||||
-webkit-transform:scale(2);-ms-transform:scale(2);transform:scale(2);
|
||||
opacity:1;
|
||||
text-shadow:2px 2px 1px rgba(0,0,0,.1);
|
||||
-webkit-transition:.1s;transition:.1s
|
||||
|
||||
.markdown-body ol li
|
||||
list-style:none;
|
||||
position:relative;
|
||||
padding:0 0 0 2.1em;
|
||||
margin:0 0 0 10px;
|
||||
text-shadow:0px 0px 0px rgba(0,0,0,.1);
|
||||
-webkit-transition:.12s;transition:.12s;
|
||||
|
||||
/*ul使用插入圆点来模拟无序列表*/
|
||||
.markdown-body ul li:before
|
||||
position:absolute;
|
||||
content:'\2022';
|
||||
font-family:Arial;
|
||||
color:#000;
|
||||
top:0;
|
||||
left:0;
|
||||
text-align:center;
|
||||
font-size:1.5em;
|
||||
opacity:.5;
|
||||
/*使用行高来调整前缀的位置*/
|
||||
line-height:1;
|
||||
text-shadow:4px 4px 1px rgba(0,0,0,.1);
|
||||
-webkit-transition:.5s;transition:.5s
|
||||
|
||||
.markdown-body ul li:hover:before
|
||||
-webkit-transform:scale(2);-ms-transform:scale(2);transform:scale(2);
|
||||
opacity:1;
|
||||
text-shadow:2px 2px 1px rgba(0,0,0,.1);
|
||||
-webkit-transition:.1s;transition:.1s
|
||||
.markdown-body ul li
|
||||
list-style:none;
|
||||
position:relative;
|
||||
padding:0 0 0 1.5em;
|
||||
margin:0 0 0 10px;
|
||||
text-shadow:0px 0px 0px rgba(0,0,0,.1);
|
||||
-webkit-transition:.12s;transition:.12s;
|
||||
|
||||
//CC
|
||||
.note.note-warning
|
||||
background-color: #a3f3f1a1;
|
||||
border-color: #ff81c0 !important;
|
||||
|
||||
//selection伪元素
|
||||
::selection
|
||||
background: #a2f1f1
|
||||
text-shadow: 0px 0px 25px
|
62
source/_data/docker/docker-compose.yml
Normal file
@ -0,0 +1,62 @@
|
||||
version: "3.2"
|
||||
services:
|
||||
#frontend
|
||||
nginx:
|
||||
container_name: nginx
|
||||
image: nginx:alpine
|
||||
networks:
|
||||
- frontend
|
||||
- backend
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./nginx/conf.d/:/etc/nginx/conf.d:ro
|
||||
- ./certbot/conf:/etc/letsencrypt
|
||||
- ./certbot/www:/var/www/certbot
|
||||
restart: always
|
||||
certbot:
|
||||
image: certbot/certbot
|
||||
container_name: certbot
|
||||
volumes:
|
||||
- ./certbot/conf:/etc/letsencrypt
|
||||
- ./certbot/www:/var/www/certbot
|
||||
command: certonly --webroot -w /var/www/certbot -d git.defectink.com --non-interactive --agree-tos -m defect.y@outlook.com
|
||||
networks:
|
||||
- frontend
|
||||
gitea:
|
||||
container_name: gitea
|
||||
image: gitea/gitea
|
||||
volumes:
|
||||
- ./gitea:/data
|
||||
depends_on:
|
||||
- nginx
|
||||
# - db
|
||||
networks:
|
||||
- backend
|
||||
ports:
|
||||
# - "3000:3000"
|
||||
- "22:22"
|
||||
restart: always
|
||||
vlmcsd:
|
||||
container_name: vlmcsd
|
||||
build: './vlmcsd'
|
||||
restart: always
|
||||
ports:
|
||||
- "1688:1688"
|
||||
# db:
|
||||
# image: mariadb
|
||||
# environment:
|
||||
# MYSQL_ROOT_PASSWORD: 68682582
|
||||
# MYSQL_DATABASE: gitea
|
||||
# MYSQL_USER: gitea
|
||||
# MYSQL_PASSWORD: 68682582
|
||||
# volumes:
|
||||
# - ./db/:/var/lib/mysql
|
||||
# networks:
|
||||
# - backend
|
||||
# restart: always
|
||||
networks:
|
||||
frontend:
|
||||
backend:
|
45
source/_data/docker/nginx/conf.d/default.conf
Normal file
@ -0,0 +1,45 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
#charset koi8-r;
|
||||
#access_log /var/log/nginx/host.access.log main;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
#error_page 404 /404.html;
|
||||
|
||||
# redirect server error pages to the static page /50x.html
|
||||
#
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
|
||||
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
|
||||
#
|
||||
#location ~ \.php$ {
|
||||
# proxy_pass http://127.0.0.1;
|
||||
#}
|
||||
|
||||
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
|
||||
#
|
||||
#location ~ \.php$ {
|
||||
# root html;
|
||||
# fastcgi_pass 127.0.0.1:9000;
|
||||
# fastcgi_index index.php;
|
||||
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
|
||||
# include fastcgi_params;
|
||||
#}
|
||||
|
||||
# deny access to .htaccess files, if Apache's document root
|
||||
# concurs with nginx's one
|
||||
#
|
||||
#location ~ /\.ht {
|
||||
# deny all;
|
||||
#}
|
||||
}
|
||||
|
40
source/_data/docker/nginx/conf.d/gitea.conf
Normal file
@ -0,0 +1,40 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name git.defectink.com;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name git.defectink.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/git.defectink.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/git.defectink.com/privkey.pem;
|
||||
|
||||
#include /data/letsencrypt/options-ssl-nginx.conf;
|
||||
#ssl_dhparam /data/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
location / {
|
||||
set $upstream "site_upstream";
|
||||
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-Port $server_port;
|
||||
proxy_set_header X-Real-Scheme $scheme;
|
||||
proxy_set_header X-NginX-Proxy true;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Ssl on;
|
||||
|
||||
proxy_pass http://gitea:3000;
|
||||
}
|
||||
}
|
29
source/_data/docker/nginx/nginx.conf
Normal file
@ -0,0 +1,29 @@
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
sendfile on;
|
||||
#tcp_nopush on;
|
||||
|
||||
keepalive_timeout 65;
|
||||
|
||||
gzip on;
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
1
source/_data/docker/vlmcsd/.cci-agent.new
Normal file
@ -0,0 +1 @@
|
||||
File not found. 文件不存在。
|
15
source/_data/docker/vlmcsd/Dockerfile
Normal file
@ -0,0 +1,15 @@
|
||||
FROM alpine:latest as builder
|
||||
WORKDIR /root
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
|
||||
&& apk update \
|
||||
&& apk upgrade \
|
||||
&& apk add --no-cache git make build-base \
|
||||
&& git clone --branch master --single-branch https://gitee.com/mirrors/vlmcsd.git \
|
||||
&& cd vlmcsd/ \
|
||||
&& make
|
||||
|
||||
FROM alpine:latest
|
||||
WORKDIR /root/
|
||||
COPY --from=builder /root/vlmcsd/bin/vlmcsd /usr/bin/vlmcsd
|
||||
EXPOSE 1688/tcp
|
||||
CMD [ "/usr/bin/vlmcsd", "-D", "-d" ]
|
@ -268,3 +268,228 @@ DOM 分为多个级别,也包含多个部分,因此检测浏览器实现了
|
||||
* nodeValue 为 null;
|
||||
* parentNode 可能是 Document 或 Element;
|
||||
|
||||
要取得元素的标签名,除了使用`nodeName`外,还可以使用`tagName`。二者返回的值相等。
|
||||
|
||||
```js
|
||||
ele.tagName == ele.nodeName // true
|
||||
```
|
||||
|
||||
#### HTML 元素
|
||||
|
||||
HTMLElement 类型继承自 Element 并添加了一些属性,所有的 HTML 元素都由他们和其子类型表示。
|
||||
|
||||
可以直接访问/修改 HTML 元素的属性:
|
||||
|
||||
```html
|
||||
<div id="the_div" class="active" title="balabala">
|
||||
```
|
||||
|
||||
```js
|
||||
let div = document.querySelector('.active');
|
||||
div.id;
|
||||
div.className;
|
||||
div.title;
|
||||
```
|
||||
|
||||
#### 取得特性
|
||||
|
||||
`element.getAttribute()`可以获取非元素属性的 Attribute 值,通常用来设置一些自定义的值。
|
||||
|
||||
```html
|
||||
<div id="the_div" class="active" title="balabala" data-v-xfy="balabala">
|
||||
```
|
||||
|
||||
```js
|
||||
let div = document.querySelector('#the_div');
|
||||
div.getAttribute('data-v-xfy');
|
||||
```
|
||||
|
||||
行内样式 style 和 onclick 这两个属性通过`element.getAttribute()`返回的值并不相同。
|
||||
|
||||
style 属性返回的是以编程方式访问的元素样式,onclick 返回的是相应的 JavaScript 代码字符串。
|
||||
|
||||
#### 设置特性
|
||||
|
||||
`element.setAttribute()`可以设置 Attribute 值,主要作用与`element.getAttribute()`相同。
|
||||
|
||||
#### attributes
|
||||
|
||||
attributes 返回一个 NamedNodeMap 与 NodeList 类似,也是一个“动态”的集合。
|
||||
|
||||
它也拥有类似的方法:
|
||||
|
||||
* `getNamedItem(name)`
|
||||
* `setNamedItem(name)`
|
||||
* `removeNamedItem(name)`
|
||||
* `item(pos)`
|
||||
|
||||
当然它还是一个类数组,可以直接使用方括号语法来访问对应的属性:
|
||||
|
||||
```js
|
||||
div.attributes['id']
|
||||
// id="the_div"
|
||||
div.attributes[0]
|
||||
// data-v-xfy="balabala"
|
||||
|
||||
div.attributes[0].nodeValue
|
||||
// "balabala"
|
||||
```
|
||||
`attributes`方法虽然不常用,但是它可以用于遍历元素的所有 Attributes:
|
||||
|
||||
```js
|
||||
function outputAttributes(node) {
|
||||
let pairs = [];
|
||||
for (let i of node.attributes) {
|
||||
pairs.push(`${i.nodeName}=${i.nodeValue}`)
|
||||
}
|
||||
return pairs.join(' ');
|
||||
}
|
||||
```
|
||||
|
||||
#### 创建元素
|
||||
|
||||
使用`document.createElement()`方法可以创建新元素,同时还可以设置元素的特性,添加更多的子节点,以及执行其他操作。
|
||||
|
||||
```js
|
||||
let div = document.createElement('div');
|
||||
div.id = 'root';
|
||||
div.className = 'active';
|
||||
```
|
||||
|
||||
> IE 支持传入整个完整的元素标签,也可以包含属性。
|
||||
|
||||
#### 元素的子节点
|
||||
|
||||
`element.childNodes`属性能够遍历所有的子节点,不仅仅只是元素节点。
|
||||
|
||||
```js
|
||||
for (let i of div.childNodes) {
|
||||
if (i.nodeType == 1) {
|
||||
// do something...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
此方法只会遍历直接子元素,如果要取得包含的所有子元素,可以使用`getElementsByTagName('')`:
|
||||
|
||||
```js
|
||||
let ul = document.querySelector('ul');
|
||||
let li = ul.getElementsByTagName('li');
|
||||
```
|
||||
|
||||
### Text 类型
|
||||
|
||||
文本节点由 Text 类型表示。纯文本中可以包含转义后的 HTML 字符,但是不能包含 HTML 代码。
|
||||
|
||||
* nodeType 为 3;
|
||||
* nodeName 为 `#text`;
|
||||
* nodeValue 的值为节点所包含的文本;
|
||||
* parentNode 是一个 Element;
|
||||
|
||||
在默认情况下,每个可以包含内容的元素最多只能有一个文本节点,而且必须确实有内容存在。
|
||||
|
||||
#### 创建文本节点
|
||||
|
||||
使用`document.createTextNode()`可以创建新文本节点。
|
||||
|
||||
```js
|
||||
var element = document.createElement("div");
|
||||
element.className = "message";
|
||||
|
||||
var textNode = document.createTextNode("Hello world!");
|
||||
element.appendChild(textNode);
|
||||
|
||||
document.body.appendChild(element);
|
||||
```
|
||||
|
||||
在某些情况下,一个元素内也可能包含多个子文本节点。
|
||||
|
||||
```js
|
||||
var element = document.createElement("div");
|
||||
element.className = "message";
|
||||
|
||||
var textNode = document.createTextNode("Hello world!");
|
||||
element.appendChild(textNode);
|
||||
|
||||
var anotherTextNode = document.createTextNode("Yippee!");
|
||||
element.appendChild(anotherTextNode);
|
||||
|
||||
document.body.appendChild(element);
|
||||
```
|
||||
|
||||
如果两个文本节点是相邻的同胞节点,那么这两个节点中的文本就会连起来显示,中间不会有空格。
|
||||
|
||||
#### 规范化文本节点
|
||||
|
||||
当一个元素中存在多个文本节点时,就难以分清哪个节点对应哪些文本。在父元素下使用`normalize()`方法可以所有文本节点合并成一个节点。这个方法是由 Node 定义的,因而在所有节点类型中都存在。
|
||||
|
||||
```js
|
||||
var element = document.createElement("div");
|
||||
element.className = "message";
|
||||
|
||||
var textNode = document.createTextNode("Hello world!");
|
||||
element.appendChild(textNode);
|
||||
|
||||
var anotherTextNode = document.createTextNode("Yippee!");
|
||||
element.appendChild(anotherTextNode);
|
||||
|
||||
document.body.appendChild(element);
|
||||
|
||||
console.log(element.childNodes.length) // 2
|
||||
element.normalize()
|
||||
console.log(element.childNodes.length) // 1
|
||||
```
|
||||
|
||||
#### 分割节点
|
||||
|
||||
与 `normalize()`相反,`splitText()`用于按照指定位置分割文本节点。
|
||||
|
||||
```js
|
||||
element.childNodes.splitText(5)
|
||||
```
|
||||
|
||||
### Comment 类型
|
||||
|
||||
注释是通过 Comment 类型来表示的。
|
||||
|
||||
* nodeType 为 8;
|
||||
* nodeName 为 `#comment`;
|
||||
* nodeValue 为注释的内容;
|
||||
* parentNode 可能是 Document 或 Element;
|
||||
* 不支持(没有)子节点;
|
||||
|
||||
浏览器不会识别位于`<html>`标签外的注释。
|
||||
|
||||
### DocumentType 类型
|
||||
|
||||
* nodeType 为 10;
|
||||
* nodeName 为 doctype 名;
|
||||
* nodeValue 为 null
|
||||
* parentNode 为 Document;
|
||||
* 不支持(没有)子节点;
|
||||
|
||||
在 DOM1 级中,DocumentType 对象不能动态创建。
|
||||
|
||||
### DocumentFragment 类型
|
||||
|
||||
DocumentFragment 是一种文档片段,它可以在插入文档前操作 DOM,不会像完整文档那样占用资源。
|
||||
|
||||
* nodeType 为 11;
|
||||
* nodeName 为 `#document-fragment`;
|
||||
* nodeValue 为 null;
|
||||
* parentNode 为 null;
|
||||
* 子节点可以是大部分节点类型;
|
||||
|
||||
```js
|
||||
let frag = document.createDocumentFragment();
|
||||
let ul = document.createElement('ul');
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
let li = document.createElement('li');
|
||||
li.innerText = `Item ${i + 1}`
|
||||
ul.append(li);
|
||||
}
|
||||
|
||||
frag.append(ul);
|
||||
```
|
||||
|
||||
|
@ -1,5 +0,0 @@
|
||||
```bash
|
||||
docker pull nginx:alpine
|
||||
docker run --rm nginx:alpine cat /etc/nginx/nginx.conf > nginx.conf
|
||||
docker run --rm nginx:alpine cat /etc/nginx/conf.d/default.conf > default.conf
|
||||
```
|
@ -1,90 +0,0 @@
|
||||
## 使用
|
||||
|
||||
在页面中引入jQuery有多种方法,最常用的方法就是下载jQuery库到本地和使用公共CDN。jQuery 库是一个 JavaScript 文件,可以直接使用`script`标签引入到html页面中。
|
||||
|
||||
```html
|
||||
<head>
|
||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.js"></script>
|
||||
</head>
|
||||
```
|
||||
|
||||
另外,现在的jQuery也支持在node上下载和使用。
|
||||
|
||||
```js
|
||||
npm install jquery
|
||||
yarn add jquery
|
||||
```
|
||||
|
||||
[下载]:(https://jquery.com/download/)
|
||||
|
||||
## 语法
|
||||
|
||||
jQuery简化了原生JS对DOM的操作,语法也与原生JS不尽相同。jQuery 语法是通过选取 HTML 元素,并对选取的元素执行某些操作。
|
||||
|
||||
基础语法: $(selector).action()
|
||||
|
||||
* 美元符号定义 jQuery
|
||||
* 选择符(selector)"查询"和"查找" HTML 元素
|
||||
* jQuery 的 action() 执行对元素的操作
|
||||
|
||||
示例:
|
||||
|
||||
* `$(this).hide()` - 隐藏当前元素
|
||||
* `$("p").hide()` - 隐藏所有 <p> 元素
|
||||
* `$("p.test").hide()` - 隐藏所有 class="test" 的 <p> 元素
|
||||
* `$("#test").hide()` - 隐藏 id="test" 的元素
|
||||
|
||||
> jQuery 使用的语法是 XPath 与 CSS 选择器语法的组合。
|
||||
|
||||
### 文档就绪函数
|
||||
|
||||
文档就绪函数是问了防止jQuery在DOM没有加载完之前就开始运行的一个函数,它通常是这样的:
|
||||
|
||||
```js
|
||||
$(document).ready(function() {
|
||||
// code
|
||||
})
|
||||
```
|
||||
|
||||
如果在文档没有完全加载之前就运行函数,操作可能失败。下面是两个具体的例子:
|
||||
|
||||
* 试图隐藏一个不存在的元素
|
||||
* 获得未完全加载的图像的大小
|
||||
|
||||
文档就绪函数还有一个简洁的写法,效果完全一样。
|
||||
|
||||
```js
|
||||
$(function() {
|
||||
// code
|
||||
})
|
||||
```
|
||||
|
||||
原生JS也有类似的入口函数:
|
||||
|
||||
```js
|
||||
window.onload = function () {
|
||||
// code
|
||||
}
|
||||
```
|
||||
|
||||
jQuery 入口函数与 JavaScript 入口函数的区别:
|
||||
|
||||
* jQuery 的入口函数是在 html 所有标签(DOM)都加载之后,就会去执行。
|
||||
* JavaScript 的 window.onload 事件是等到所有内容,包括外部图片之类的文件加载完后,才会执行。
|
||||
|
||||
<p style="text-align: center">load和ready区别<p>
|
||||
|
||||
| | window.onload() | $(document).ready() |
|
||||
| -------- | ---------------------------------------------- | --------------------------------------- |
|
||||
| 执行时机 | 必须等待网页加载完才能执行(包括图片等) | 只需等待网页中的DOM结构加载完毕就能执行 |
|
||||
| 执行次数 | 只能执行一次,如果第二次,那么第一次就会被覆盖 | 可以执行多次,多次都不会被覆盖 |
|
||||
| 简写方式 | 无 | $(function() { <br> // code <br> }) |
|
||||
|
||||
## 选择器
|
||||
|
||||
jQuery 中所有选择器都以美元符号开头:`$()`。选择器基于元素的 id、类、类型、属性、属性值等"查找"(或选择)HTML 元素。 它基于已经存在的 CSS 选择器,除此之外,它还有一些自定义的选择器。
|
||||
|
||||
## 事件
|
||||
|
||||
jQuery 是为事件处理特别设计的。
|
||||
|
@ -1,15 +0,0 @@
|
||||
## Nginx 与反代
|
||||
|
||||
在很久以前,曾今尝试过将自己当时的小破站[所有的服务都使用 Docker 来部署](https://www.defectink.com/defect/docker-container-all.html),在未来迁移也会更加方便。也就是那时,正真的用上了 Docker。
|
||||
|
||||
不过这次水的部分不同,没想到过直接把小破站迁移到了 Hexo。这次准备搭建一些其他的服务,而有些东西对于 SSL 的配置很是复杂,并且多个应用在一起端口也不方便分配。于是就想到了使用 Nginx 来做反代。
|
||||
|
||||
## Nginx 的安装与配置
|
||||
|
||||
### 修改配置文件
|
||||
|
||||
## 使用 certbot 获取证书
|
||||
|
||||
### 为 Nginx 配置 SSL
|
||||
|
||||
### 自动续期
|
27
source/_md/容器化!为Gitea添加SSL.md
Normal file
@ -0,0 +1,27 @@
|
||||
## Nginx 与反代
|
||||
|
||||
在很久以前,曾今尝试过将自己当时的小破站[所有的服务都使用 Docker 来部署](https://www.defectink.com/defect/docker-container-all.html),在未来迁移也会更加方便。也就是那时,正真的用上了 Docker。
|
||||
|
||||
不过这次水的部分不同,没想到过直接把小的配置很是复杂,并且多个应用在一起端口也不方便分配。于是就想到了使用 Nginx 来做反代。
|
||||
|
||||
## Nginx 的配置
|
||||
|
||||
安装已是不在话下的事情,主要还是优雅的获取配置文件。
|
||||
|
||||
```bash
|
||||
docker run --rm nginx:alpine cat /etc/nginx/nginx.conf > nginx.conf
|
||||
```
|
||||
|
||||
```bash
|
||||
docker run --rm nginx:alpine cat /etc/nginx/conf.d/ > conf.d/
|
||||
```
|
||||
|
||||
### 修改配置文件破站迁移到了 Hexo。这次准备搭建一些其他的服务,而有些东西对于 SSL
|
||||
|
||||
在之后的 compose 文件中可以再将其映射回去。
|
||||
|
||||
## 使用 certbot 获取证书
|
||||
|
||||
### 为 Nginx 配置 SSL
|
||||
|
||||
### 自动续期
|
@ -1,70 +0,0 @@
|
||||
为了深入了解同步与异步编程,和充分理解这一理念。打算详细的了解与对比这两种编程模式。
|
||||
|
||||
## Node编程风格
|
||||
|
||||
随便搜寻几篇 Node.js 的文章便会发现,node 主要的编程风格便是使用非阻塞(异步)编码风格。先通过一个日常的举例来了解下阻塞(同步)编码与非阻塞(异步)编码之间的差异。
|
||||
|
||||
## 同步与异步的对比 – 场景一
|
||||
|
||||
Node.js 官网关于阻塞式 I/O 的定义如下:
|
||||
|
||||
> “阻塞表示 Node.js 进程中其他 JavaScript 的执行必须等到非 JavaScript 操作完成后才能继续的情况。发生这种情况的原因在于,当发生阻塞操作时,事件循环无法继续运行 JavaScript。
|
||||
> 在 Node.js 中,由于 CPU 占用率高而不是等待非 JavaScript 操作(如 I/O)导致性能欠佳的 JavaScript 通常并不被称为阻塞。”
|
||||
|
||||
### 同步
|
||||
|
||||
普通的同步风格程序。它在 V8 线程上自上而下运行,仅占用少量 CPU,这是非技术层面的阻塞。
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
console.log(Date.now().toString() + ': 主进程开始;');
|
||||
console.log(Date.now().toString() + ': 创造一个延迟;');
|
||||
let start = Date.now();
|
||||
let end = start;
|
||||
while(end < start + 20) {
|
||||
end = Date.now(); // 阻塞延迟
|
||||
}
|
||||
console.log(Date.now().toString() + ': 主进程结束;');
|
||||
```
|
||||
|
||||
这里使用了`while`通过不断的检查系统时间来创建一个阻塞的延迟动作,根据最后打印的时间戳,总共运行时间在 21~23 毫秒之间。
|
||||
|
||||
### 异步
|
||||
|
||||
使用异步的方式创建了与上述同样效果的实例。
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
console.log(Date.now().toString() + ': 主进程开始;');
|
||||
setTimeout(() => {
|
||||
console.log(Date.now().toString() + ': 事件循环回调');
|
||||
}, 20);
|
||||
console.log(Date.now().toString() + ': 主进程结束;');
|
||||
```
|
||||
|
||||
这里的异步使用了`setTimeout`来将事件超时运行。根据最后打印的时间戳,总共运行时间在 25~30 毫秒之间。
|
||||
|
||||
## 场景一小结
|
||||
|
||||
经过上述的小实验,不难发现同步的运行时间比异步还短那么几毫秒。异步反而更慢,异步编程风格并不关乎单纯的速度,而是关乎可扩展性。
|
||||
|
||||
## 模块系统
|
||||
|
||||
模块化是现代软件开发中的一个关键概念。它使我们能够构建更健壮的代码,并在多处复用代码,而无需重复编写相同的代码。
|
||||
|
||||
Node 模块化不仅为我们提供了上述所有好处,还提供了:
|
||||
|
||||
* 自动封装。默认情况下,一个模块中的所有代码都被打包到函数包装程序中,以便对模块外部的其他 JavaScript 代码隐藏。
|
||||
|
||||
* 一种公开模块接口的方式。模块中的函数、变量和其他构造必须通过 module.exports(或者其简短表示:exports)对模块外部的 JavaScript 模块显式公开。
|
||||
|
||||
### 引入模块
|
||||
|
||||
在 ES6 发布后,module 成为标准。曾经的我们采用的是 CommonJS 规范,使用`require()`来引入模块。目前的 ES6 标准使用`import`来引入模块。
|
||||
|
||||
```js
|
||||
let fs = require('fs');
|
||||
import fs from 'fs';
|
||||
```
|
||||
|
||||
ES2015 的 module
|
@ -77,7 +77,7 @@ data() {
|
||||
```js
|
||||
activated() {
|
||||
console.log("activated");
|
||||
if (!(location.pathname === this.path)) {
|
||||
if (!(this.$route.path === this.path)) {
|
||||
this.$router.push(this.path);
|
||||
}
|
||||
},
|
||||
@ -87,7 +87,7 @@ beforeRouteLeave(to, from, next) {
|
||||
},
|
||||
```
|
||||
|
||||
另外这个判断是必须的:`if (!(location.pathname === this.path))`。如果不判断当前的 URL 是否与缓存的 URL 一致,那么当组件激活时就会无条件的运行`this.$router.push(this.path);`,导致同一条路由被重写两遍。
|
||||
另外这个判断是必须的:`if (!(this.$route.path === this.path))`。如果不判断当前的 URL 是否与缓存的 URL 一致,那么当组件激活时就会无条件的运行`this.$router.push(this.path);`,导致同一条路由被重写两遍。
|
||||
|
||||
并得到这样的错误:
|
||||
|
||||
@ -101,4 +101,7 @@ Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to curr
|
||||
|
||||
### 不缓存呢
|
||||
|
||||
在组件被创建时,`data`方法也会初始化,其中的值也会跟着初始化,没有办法记录路由离开前的 URL。
|
||||
在组件被创建时,会调用`created`方法,但是`data`方法也会初始化,其中的值也会跟着初始化,没有办法记录路由离开前的 URL。
|
||||
|
||||
如果将其`path`存储在组件之外那么也可以实现记录 URL,不过这种情况还是在组件内定义单独的数据为好,并用不上 Vuex。
|
||||
|
||||
|
344
source/_posts/某咸鱼的ToDoList实践.md
Normal file
@ -0,0 +1,344 @@
|
||||
---
|
||||
title: 某咸鱼的 ToDoList 实践🐟
|
||||
date: 2021-02-11 17:47:25
|
||||
tags: [JavaScript, Vue]
|
||||
categories: 实践
|
||||
url: todolist-exercise
|
||||
index_img: /images/某咸鱼的ToDoList实践/logo.webp
|
||||
---
|
||||
|
||||
## 为什么要迫害 ToDoList
|
||||
|
||||
摸鱼了也挺长时间,是时候该尝试写个小[案例](https://defectingcat.github.io/todolist/)安慰下自己了。
|
||||
|
||||

|
||||
|
||||
## 组件划分
|
||||
|
||||
对于 ToDoList 来说,没有多少组件化的东西。主要还是对一些基本知识的练习。所以我将其划分了一个父组件和两个子组件,他们分别是:用于输入的 Button 和用于展示数据 List。
|
||||
|
||||
两个子组件分别对存在于父组件内保存的列表进行增删改的操作。
|
||||
|
||||
## 整体功能
|
||||
|
||||
目录结构:
|
||||
|
||||
```
|
||||
$ tree -l 4 --ignore 'node_modules'
|
||||
└── src
|
||||
├── App.vue
|
||||
├── assets
|
||||
| ├── css
|
||||
| | └── base.css
|
||||
| └── logo.png
|
||||
├── components
|
||||
| ├── common
|
||||
| | ├── InputButton.vue
|
||||
| | └── Lists.vue
|
||||
| └── content
|
||||
| └── MainTab.vue
|
||||
└── main.js
|
||||
```
|
||||
|
||||
MainTab 作为两个子组件 InputButton 和 Lists 的父组件:
|
||||
|
||||
```html
|
||||
<div class="main-tab">
|
||||
<InputButton @addMsg="addMsg" />
|
||||
<Lists :lists="lists" @removeItem="removeItem" @editMsg="editMsg" />
|
||||
</div>
|
||||
```
|
||||
|
||||
### 添加数据
|
||||
|
||||
InputButton 在 DOM 上添加一个 Input 与 Button 按钮,负责向父组件的 data 添加数据。在 InputButton 内:
|
||||
|
||||
```html
|
||||
<div>
|
||||
<input type="text" v-model="msg" @keyup.enter="emitMsg" />
|
||||
<button @click="emitMsg">+</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
先使用`v-model`将其输入的数据双向绑定到子组件内的一个变量,然后再在按钮上绑定对应的方法将其发送给父组件。
|
||||
|
||||
```js
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
msg: ''
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
emitMsg() {
|
||||
this.$emit('addMsg', this.msg);
|
||||
this.msg = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
父组件这边使用一个数组来保存数据:
|
||||
|
||||
```
|
||||
[{"id":1,"msg":"这是一个添加测试"},{"id":0,"msg":"这是一个测试"}]
|
||||
```
|
||||
|
||||
id 主要用于给`v-for`做`key`绑定用,在父组件内添加时,按照`{"id":1,"msg":"这是一个添加测试"}`的对象格式来添加即可,值得注意的就是 id 需要递增。
|
||||
|
||||
### 展示数据
|
||||
|
||||
Lists 组件通过接受父组件传递的 props 来展示数据。通过一个简单的 `v-for`循环来遍历所有数据:
|
||||
|
||||
```html
|
||||
<li v-for="(item, i) in lists" :key="item.id" :id="item.id">
|
||||
<span v-show="isEdit != item.id">
|
||||
{{ item.msg }}
|
||||
</span>
|
||||
<button @click="editItem(i, item.id)">编辑</button>
|
||||
<button @click="removeItem(i)">删除</button>
|
||||
</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
目前的 id 用来绑定 key
|
||||
|
||||
### 操作数据
|
||||
|
||||
在 Lists 组件内还需要实现删除某一项和编辑某一项的功能,由于数据都保存在父组件,所有在 Lists 内所有操作都通过`$emit`将事件发射出去。
|
||||
|
||||
```js
|
||||
methods: {
|
||||
removeItem(i) {
|
||||
this.$emit('removeItem', i);
|
||||
},
|
||||
editItem(i, id) {
|
||||
this.isEdit = id;
|
||||
this.editedMsg = this.lists[i].msg;
|
||||
},
|
||||
editMsg(i) {
|
||||
this.$emit('editMsg', i, this.editedMsg);
|
||||
this.isEdit = -1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`removeItem(i)`是一个简单的删除当前项的方法,它通过传递 lists 的下标来确定当前项。在父组件中通过数组方法`splice()`来删除当前项:
|
||||
|
||||
```js
|
||||
removeItem(i) {
|
||||
this.lists.splice(i, 1);
|
||||
},
|
||||
```
|
||||
|
||||
较为复杂的是编辑当前项的文字,首先需要解决的就是隐藏当前的文字,显示出一个`<input>`框。
|
||||
|
||||
```html
|
||||
<input
|
||||
type="text"
|
||||
v-show="isEdit == item.id"
|
||||
v-model="editedMsg"
|
||||
@keyup.enter="editMsg(i)"
|
||||
:key="item.id"
|
||||
@blur="editMsg(i)"
|
||||
/>
|
||||
<span v-show="isEdit != item.id">
|
||||
{{ item.msg }}
|
||||
</span>
|
||||
```
|
||||
|
||||
第一个想法就是通过`v-show`来判断是否显示,这里相比使用`v-if`要好点,因为这里需要来回切换。而简单的通过`true`与`false`来判断的条件是不够的,当一个添加满足了,所有的`<input>`框都会被显示出来,最终导致数据混乱。
|
||||
|
||||
这里使用一个经典的方法,以前做 tab 切换的选项卡的时候用到过。通过给`<li>`标签添加一个 ID,并保存当前数据内的 id 来判断选中的哪一个`<li>`标签。
|
||||
|
||||
```js
|
||||
data() {
|
||||
return {
|
||||
isEdit: -1,
|
||||
editedMsg: ''
|
||||
};
|
||||
},
|
||||
editItem(i, id) {
|
||||
this.isEdit = id;
|
||||
this.editedMsg = this.lists[i].msg;
|
||||
},
|
||||
```
|
||||
|
||||
然后通过`v-model`来绑定已经有的数据,再编辑完之后,发送一个事件到父组件,并传递修改的数据,在父组件内完成修改。现在使用`$emit`传递多个参数时可以直接在后面追加参数,在父组件中可以接受到多个参数。
|
||||
|
||||
并且在事件发送后就表示编辑结束,需要将用于判断`v-show`的值`isEdit: -1`修改为默认状态。
|
||||
|
||||
```js
|
||||
editMsg(i) {
|
||||
this.$emit('editMsg', i, this.editedMsg);
|
||||
this.isEdit = -1;
|
||||
}
|
||||
```
|
||||
|
||||
父组件内接受到下标和数据中可以轻松实现修改:
|
||||
|
||||
```js
|
||||
editMsg(i, message) {
|
||||
this.lists[i].msg = message;
|
||||
}
|
||||
```
|
||||
|
||||
`$emit`方法发送事件时可以传递多个参数,现在的版本支持多个参数分开传递,而不是对象的方式存储多个参数。在父组件中可以接受到多个参数。
|
||||
|
||||
### Done!
|
||||
|
||||
既然是 Todolist,那么少不了的必然是完成项目。这里用的是一个 checkbox 的`<input>`来做完成的勾选框。所以 List 组件内的结构也需要稍微变动下:
|
||||
|
||||
```html
|
||||
<input
|
||||
type="checkbox"
|
||||
:name="item.id"
|
||||
:id="item.id"
|
||||
:key="item.id"
|
||||
@change="doneItem(i)"
|
||||
/>
|
||||
<label :for="item.id">
|
||||
<span
|
||||
v-show="isEdit != item.id"
|
||||
:class="{ done: item.done }"
|
||||
@dblclick="editItem(i, item.id)"
|
||||
class="message"
|
||||
>
|
||||
{{ item.msg }}
|
||||
</span>
|
||||
</label>
|
||||
```
|
||||
|
||||
通知父组件修改数据的方法还是同样的简单,监听`@change`属性,当 checkbox 被勾选时,发送一个下标给父组件用于定位当前的数据即可。
|
||||
|
||||
```js
|
||||
doneItem(i) {
|
||||
this.$emit('doneItem', i);
|
||||
}
|
||||
```
|
||||
|
||||
这里给 lists 数组内的对象额外增加了一个`done`属性,默认值为`false`,它现在类似于这样`{ id: this.id, msg: msg, done: false }`。
|
||||
|
||||
在父组件中接受到下标时,定位到指定的属性中,将`done`属性修改为`true`则为项目完成。
|
||||
|
||||
```js
|
||||
doneItem(i) {
|
||||
this.lists[i].done
|
||||
? (this.lists[i].done = false)
|
||||
: (this.lists[i].done = true);
|
||||
}
|
||||
```
|
||||
|
||||
自然完成了一个项目后,需要将其使用~~删除线~~进行标记。这里能够轻松的拿到 boolean 值的数据,配合`v-bind`一个 class 即可实现样式的切换。
|
||||
|
||||
```html
|
||||
:class="{ done: item.done }"
|
||||
```
|
||||
|
||||
```css
|
||||
.done {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
```
|
||||
|
||||
## 美化
|
||||
|
||||
基本功能已经完成的差不多了,但是它还缺少一样灵魂,那就是 CSS。现在的样子惨目忍睹
|
||||
|
||||

|
||||
|
||||
本🐟 CSS 比较差,所以在添加样式上花了不少的时间。不过期间也学习到了一些东西,所以也所了一些记录。
|
||||
|
||||
### scoped 继承
|
||||
|
||||
在父组件中`<style>`添加`scoped`属性后,因为子组件的根组件会被渲染到父组件内,所以随机添加的 Attribute 会被继承的添加到子组件渲染后**根**的标签上。
|
||||
|
||||

|
||||
|
||||
### 基本样式
|
||||
|
||||
样式的主要思路是:将父组件`fixed`到中央,其中两个子组件通过父组件上的`flex`定位,使用`align-items: center;`实现垂直居中。组件内的垂直居中也是使用同样的方法实现的。
|
||||
|
||||
### 进度条
|
||||
|
||||
已标记为完成的项目除以总项目再乘以 100% 就是完成进度了,既然现在可以轻松绑定到 style,那么根据完成的进度就可以轻松绑定其宽度`:style="{ width: process }"`。配合上 500ms 的 transition,一个简易的进度条就做好了🎈
|
||||
|
||||
首先是使用一个 computed 来计算完成的百分比:
|
||||
|
||||
```js
|
||||
computed: {
|
||||
process() {
|
||||
let doneItem = [];
|
||||
let result = 0;
|
||||
let len = this.lists.length;
|
||||
for (let i of this.lists) {
|
||||
if (i.done) {
|
||||
doneItem.push(i);
|
||||
}
|
||||
}
|
||||
result = (doneItem.length / len) * 100;
|
||||
if (Math.floor(result)) {
|
||||
return `${Math.floor(result)}%`;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
然后就是创建一个用于显示的标签:
|
||||
|
||||
```html
|
||||
<div class="processed" :style="{ width: process }">
|
||||
```
|
||||
|
||||
## 动画
|
||||
|
||||
Vue 提供了 transition 的封装组件,可以为我们的元素过渡添加优美的动画。
|
||||
|
||||

|
||||
|
||||
在本次的案例中,使用`<transition-group>`可以为列表添加一个过渡。
|
||||
|
||||
```html
|
||||
<transition-group name="list" tag="ul">
|
||||
<li>...</li>
|
||||
<transition>
|
||||
```
|
||||
|
||||
`enter`和`leave-to`分别是进入和离开时的过渡:
|
||||
|
||||
```css
|
||||
.list-enter {
|
||||
opacity: 0;
|
||||
transform: translateY(-30px);
|
||||
}
|
||||
.list-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
}
|
||||
```
|
||||
|
||||
我们可以只在进入或离开时添加过渡动画:
|
||||
|
||||
```css
|
||||
.list-enter-active,
|
||||
.list-leave-active {
|
||||
transition: all 500ms ease;
|
||||
}
|
||||
```
|
||||
|
||||
但是这样还不够,这时只是被添加的那一个元素有了过渡动画,而其他元素都是“闪现”到其他位置的。Vue 使用了一个叫 FLIP 简单的动画队列使用 `transforms`将元素从之前的位置平滑过渡新的位置。
|
||||
|
||||
这时不需要单独的设置`.list-enter-active, .list-leave-active`的变换效果了,可以将`li`添加一个` transition: all 500ms ease;`所有变换的效果。
|
||||
|
||||
并且,在删除元素时,需要在`.list-leave-active`上添加`position: absolute`才能触发剩下元素的`v-move`属性。
|
||||
|
||||
```css
|
||||
.list-leave-active {
|
||||
/* 脱离文档才能触发v-move */
|
||||
position: absolute;
|
||||
}
|
||||
```
|
||||
|
||||
这样在添加新数据时看上去就不再那么突兀了。
|
505
source/_posts/经典轮播图的实现方案.md
Normal file
@ -0,0 +1,505 @@
|
||||
---
|
||||
title: 经典轮播图的实现方案
|
||||
date: 2021-02-09 13:37:00
|
||||
tags: [Vue, JavaScript]
|
||||
categories: 实践
|
||||
url: classic-slider-show
|
||||
index_img: /images/经典轮播图的实现方案/logo.webp
|
||||
---
|
||||
|
||||
## 传统方式
|
||||
|
||||
[项目地址](https://git.defectink.com/xfy/classic-slider)
|
||||
|
||||
传统方式实现轮播也算一个不小的工程量了,基本布局就是将所有的图片横向布局,并在首位分别克隆最后和第一张图片。当切换到克隆的图片时,并在动画播放快结束时,将这个图片队列复位。
|
||||
|
||||

|
||||
|
||||
当整个队列切换完了之后,会切换到克隆的 #1 图片上,再动画完成之后,我们悄悄的将其切换为真正的第一张图片。
|
||||
|
||||

|
||||
|
||||
## 传统方式的实现
|
||||
|
||||
为了给各种数量的图片做适应,所以整个队列的宽度就不固定死了,而使用 JavaScript 动态生成,包括图片索引的小圆点。
|
||||
|
||||
### HTML
|
||||
|
||||
整个框架非常的简单,一个外层 div 中有三个主要的结构,分别是:用于存放图片的无序列表、左右按钮和小圆点索引。
|
||||
|
||||
```html
|
||||
<div class="wrapper">
|
||||
<div class="slider" id="slider">
|
||||
<ul class="clear-fix">
|
||||
<li><img src="./img/1.jpg" alt="" class="roll-img" /></li>
|
||||
<li><img src="./img/2.png" alt="" class="roll-img" /></li>
|
||||
<li><img src="./img/3.png" alt="" class="roll-img" /></li>
|
||||
<li><img src="./img/4.jpg" alt="" class="roll-img" /></li>
|
||||
<li><img src="./img/5.jpg" alt="" class="roll-img" /></li>
|
||||
</ul>
|
||||
<div class="btn btn-left">
|
||||
<span><</span>
|
||||
</div>
|
||||
<div class="btn btn-right">
|
||||
<span>></span>
|
||||
</div>
|
||||
<div class="index-box">
|
||||
<ol></ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### CSS
|
||||
|
||||
CSS 方面,主要值得注意的就是 ul 和 li,他们分别为绝对定位`position: absolute;`和左浮动`float: left;`。
|
||||
|
||||
对 ul 使用绝对定位就可以控制其`left`属性来实现左右移动的动画效果。而对 li 使用左浮动是为了将所有的图片都排列成一行。
|
||||
|
||||
```css
|
||||
.slider {
|
||||
transition: all 300ms;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
border-radius: 14px;
|
||||
/* overflow: hidden; */
|
||||
}
|
||||
.slider ul {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -400px;
|
||||
/* width: 700%; */
|
||||
/* height: 100%; */
|
||||
list-style: none;
|
||||
}
|
||||
.slider ul li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.roll-img {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.clear-fix::after {
|
||||
content: "";
|
||||
clear: both;
|
||||
display: block;
|
||||
height: 0;
|
||||
}
|
||||
```
|
||||
|
||||
## JavaScript
|
||||
|
||||
JavaScript 方面内容值得为它写成一个二级标题。它有多个部分组成,在 JavaScript 做的事情有:
|
||||
|
||||
* 动态根据图片生成圆点;
|
||||
* 克隆需要的图片节点;
|
||||
* 动态添加小圆点样式;
|
||||
* 左右翻页按钮的监听器;
|
||||
* 移动方法;
|
||||
* 重置整个图片队列;
|
||||
|
||||
### 初始化
|
||||
|
||||
这次使用 ES6 的 class 来创建对象,并初始化整个轮播图。第一步要做的就是在`constructor()`中选中和定义需要的数据:
|
||||
|
||||
```js
|
||||
class Slider {
|
||||
constructor(id) {
|
||||
// 外层div
|
||||
this.wrapper = document.querySelector(id);
|
||||
this.pics = this.wrapper.querySelector('ul');
|
||||
// 小圆点外层
|
||||
this.index = this.wrapper.querySelector('.index-box');
|
||||
// 小圆点索引
|
||||
this.indexPoint = 1;
|
||||
// 单个图片宽度
|
||||
this.picWidth = this.wrapper.clientWidth;
|
||||
// 总共图片个数
|
||||
this.sliders = this.pics.children.length;
|
||||
// 按钮点击间隔
|
||||
this.startMs = Date.now();
|
||||
// 点击小圆点的间隔
|
||||
this.movePoint = 0;
|
||||
// 初始化方法
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`init()`方法中主要就是其他一些方法的集中运行,他们分别是:初始化圆点、克隆图片和左右按钮:
|
||||
|
||||
```js
|
||||
init() {
|
||||
console.log('init!');
|
||||
this.initPoint();
|
||||
this.clonePic();
|
||||
this.lR();
|
||||
}
|
||||
```
|
||||
|
||||
### 初始化圆点
|
||||
|
||||
初始化圆点主要做的三件事:
|
||||
|
||||
1. 根据图片数量生成圆点个数;
|
||||
2. 为第一个圆点添加激活的样式;
|
||||
3. 监听点击事件;
|
||||
|
||||
这里使用了 document fragment 来将所有生成的圆点一次性的插入到 DOM 里,从而减少操作 DOM 的频率。
|
||||
|
||||
其中比较复杂的部分在于监听点击事件,它不仅仅要为点击的圆点添加相应激活的样式,还要判断圆点的左右和距离来移动对应距离的图片。
|
||||
|
||||
在事件代理里,需要做一个小判断`if (e.target.nodeName.toString().toLowerCase() == 'li')`,不然点击到了父 div 元素会取不到对应的属性而报错。
|
||||
|
||||
这里判断点击圆点后的左右移动使用时,定义了两个变量,他们分别是:
|
||||
|
||||
```js
|
||||
// 小圆点索引
|
||||
this.indexPoint = 1;
|
||||
// 点击小圆点的间隔
|
||||
this.movePoint = 0;
|
||||
```
|
||||
|
||||
`this.indexPoint`就是自定义的 Attribute,通过判断`this.indexPoint`上一次和这一次点击的大小,就可以判断出点击的圆点是在左边还是在右边,从而向对应的方向移动图片。
|
||||
|
||||
而图片移动的距离使则就是`小圆点的间隔 * 单张图片的宽度`,这里调用的是自定义的`move()`方法,它接受的参数就是图片移动的距离`this.move(-this.picWidth * this.movePoint);`。
|
||||
|
||||
`this.picWidth`是提前定义好的单张图片的宽度,通过`this.wrapper.clientWidth;`来获取
|
||||
|
||||
```js
|
||||
initPoint() {
|
||||
// 获取图片的总个数
|
||||
let num = this.pics.children.length;
|
||||
// 使用 fragment 片段来只操作一次 DOM 插入所有圆点
|
||||
let frg = document.createDocumentFragment();
|
||||
// 循环生成
|
||||
for (let i = 0; i < num; i++) {
|
||||
let li = document.createElement('li');
|
||||
// 设置自定义 Attribute,用来标识和应用样式;
|
||||
li.setAttribute('data-index', i + 1);
|
||||
// 默认激活状态
|
||||
if (i == 0) {
|
||||
li.className = 'active';
|
||||
}
|
||||
// 插入到 fragment
|
||||
frg.append(li);
|
||||
}
|
||||
// 动态生成 ol 宽度
|
||||
this.index.children[0].style.width = num * 10 * 2 + 'px';
|
||||
this.index.children[0].append(frg);
|
||||
// 添加点击事件,获取index
|
||||
this.index.children[0].addEventListener('click', (e) => {
|
||||
// 利用事件冒泡做事件代理,只点击到 li 元素生效
|
||||
if (e.target.nodeName.toString().toLowerCase() == 'li') {
|
||||
let ip = e.target.getAttribute('data-index');
|
||||
// 判断点击的圆点间隔,从而决定移动多少图片
|
||||
if (this.indexPoint < ip) {
|
||||
this.movePoint = ip - this.indexPoint;
|
||||
this.indexPoint = Number(ip);
|
||||
// 调用 move() 方法来移动图片,移动的距离为圆点的间隔 * 单张图片的宽度
|
||||
this.move(-this.picWidth * this.movePoint);
|
||||
} else {
|
||||
this.movePoint = this.indexPoint - ip;
|
||||
this.indexPoint = Number(ip);
|
||||
this.move(this.picWidth * this.movePoint);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 克隆节点
|
||||
|
||||
前面曾提到过,需要手动的克隆前后两张图片,并再将其放调换放在前后。由于在第一张图片的前面添加了一张克隆的最后一张图,所以需要将 ul 的左偏移提前设置为负的一张图片的宽度。
|
||||
|
||||
并且整个 ul 的宽度为所有图片加在一起的宽度。
|
||||
|
||||
```js
|
||||
clonePic() {
|
||||
// 克隆第一个元素
|
||||
let first = this.pics.firstElementChild.cloneNode(true);
|
||||
// 克隆最后一个元素
|
||||
let last = this.pics.lastElementChild.cloneNode(true);
|
||||
this.pics.append(first);
|
||||
this.pics.insertBefore(last, this.pics.firstElementChild);
|
||||
// 动态设定宽度
|
||||
this.pics.style.width = this.pics.children.length * 100 + '%';
|
||||
// 动态设定漂移
|
||||
this.pics.style.left = -1 * this.picWidth + 'px';
|
||||
}
|
||||
```
|
||||
|
||||
### 左右按钮
|
||||
|
||||
左右翻页按钮的基本框架在 HTML 中已经写好,接下来就是动态的监听事件了。
|
||||
|
||||
这两个按钮里主要就是监听点击的事件,主要的功能还是在移动方法`move()`里。
|
||||
|
||||
这里还在每次点击结束后记录了下当前的时间戳,当再次点击按钮时会比较当前点击的时间戳和记录的时间戳,如果小于 310ms 则什么也不做。
|
||||
|
||||
```js
|
||||
lR() {
|
||||
// 选中左右按钮,并添加监听器
|
||||
this.wrapper.querySelector('.btn-left').addEventListener('click', () => {
|
||||
// 记录上一次点击的时间戳,310ms 内的连续点击将不生效
|
||||
if (Date.now() - this.startMs > 310) {
|
||||
// 圆点越界时重置其 index
|
||||
if (this.indexPoint - 1 < 1) {
|
||||
this.indexPoint = this.sliders;
|
||||
} else {
|
||||
this.indexPoint--;
|
||||
}
|
||||
this.move(this.picWidth);
|
||||
this.startMs = Date.now();
|
||||
}
|
||||
});
|
||||
this.wrapper.querySelector('.btn-right').addEventListener('click', () => {
|
||||
// 记录上一次点击的时间戳,310ms 内的连续点击将不生效
|
||||
if (Date.now() - this.startMs > 310) {
|
||||
// 圆点越界时重置其 index
|
||||
if (this.indexPoint + 1 > this.sliders) {
|
||||
this.indexPoint = 1;
|
||||
} else {
|
||||
this.indexPoint++;
|
||||
}
|
||||
this.move(-this.picWidth);
|
||||
this.startMs = Date.now();
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 移动方法
|
||||
|
||||
在前面的按钮里调用了移动方法`move()`,并传递了一个宽度作为参数。这个参数就用作于 css 的`left`值,当为负数时整个队列向左移动,反之,向右移动。
|
||||
|
||||
仔细一看也不是非常的复杂,基本的移动方法就是:获取当前的`left`值,然后加上传递进来的 width 参数,最后最为最新的`left`值赋值到 ul 上。
|
||||
|
||||
最重要的,也就是整个轮播图的核心部分就是重置队列那部分。当当前的`left`值等于负的第一张图片时,也就是队列滚动到了克隆的 #5 图片上了。图片切换动画的时间是 300ms,在第 295ms 时我们就需要悄悄的将 ul 的动画移除,并将整个队列移动到正真的第五张图片上。这样就可以实现无缝的轮播滚动了。反之亦然。
|
||||
|
||||

|
||||
|
||||
```js
|
||||
move(width) {
|
||||
// 获取当前的 left 值
|
||||
let left = parseFloat(this.pics.style.left);
|
||||
// 添加 300ms 的动画
|
||||
this.pics.style.transition = `all 300ms`;
|
||||
// 通过移动正负值来确定左右
|
||||
this.pics.style.left = left + width + 'px';
|
||||
// 狸猫换太子
|
||||
if (left == -this.picWidth && width > 0) {
|
||||
// 动画为300ms,当为最后一张图的时候,295ms后替换整个ul
|
||||
setTimeout(() => {
|
||||
this.pics.style.transition = `none`;
|
||||
this.pics.style.left = -this.sliders * this.picWidth + 'px';
|
||||
}, 295);
|
||||
} else if (left == -this.sliders * this.picWidth && width < 0) {
|
||||
setTimeout(() => {
|
||||
this.pics.style.transition = `none`;
|
||||
this.pics.style.left = -this.picWidth + 'px';
|
||||
}, 290);
|
||||
}
|
||||
// 应用圆点样式
|
||||
this.modIndexPoint();
|
||||
}
|
||||
```
|
||||
|
||||
### 动态添加圆点样式
|
||||
|
||||
最后一个小功能就是动态的添加圆点的激活样式,这个部分非常的简单,一些基本操作。
|
||||
|
||||
首先将所有的圆点的样式都移除,然后在索引的圆点上添加`active`的样式。
|
||||
|
||||
```js
|
||||
modIndexPoint() {
|
||||
let points = Array.from(this.index.children[0].children);
|
||||
for (let i of points) {
|
||||
i.className = '';
|
||||
}
|
||||
points[this.indexPoint - 1].className = 'active';
|
||||
}
|
||||
```
|
||||
|
||||
## Vue
|
||||
|
||||
[项目地址](https://git.defectink.com/xfy/vue-slider)
|
||||
|
||||
Vue 由数据驱动,配合上其过渡样式,可以轻松实现另一种无限轮播的方案。
|
||||
|
||||
### 数据驱动
|
||||
|
||||
将所有的图片都使用`position: absolute`定位保持在同一个位置,然后配合`v-show`来显示图片。当图片能够成功显示时,配合 Vue 的过渡动画就能够实现无缝的切换了。
|
||||
|
||||
这里使用了`v-for`来动态的创建图片 DOM,外层由`transition-group`定义为 ul,而内部 li 则封装为一个组件:
|
||||
|
||||
```html
|
||||
<transition-group :name="dest" tag="ul" class="img">
|
||||
<ImgItem v-for="(item, i) in images" :key="item.id" v-show="show == i">
|
||||
<template>
|
||||
<img :src="item.src" />
|
||||
</template>
|
||||
</ImgItem>
|
||||
</transition-group>
|
||||
```
|
||||
|
||||
而`v-show`呢,则配合 images 列表的下标显示,当图片的下标与定义的变量 show 相同时`v-show="show == i"`则显示这个图片。
|
||||
|
||||
```js
|
||||
data() {
|
||||
return {
|
||||
images: [
|
||||
{ id: 1, src: require('../../assets/img/1.webp') },
|
||||
{ id: 2, src: require('../../assets/img/2.webp') },
|
||||
{ id: 3, src: require('../../assets/img/3.webp') },
|
||||
{ id: 4, src: require('../../assets/img/4.webp') },
|
||||
{ id: 5, src: require('../../assets/img/5.webp') }
|
||||
],
|
||||
show: 0,
|
||||
}
|
||||
```
|
||||
|
||||
### 切换图片
|
||||
|
||||
在考虑过渡动画之前,应该先把切换图片的功能做好。
|
||||
|
||||
切换功能非常的简单,刚刚定义的 show 变量便是驱动图片显示的数据。而切换图片只要增加和减少这个 show 变量即可,只需要注意防止其越界。
|
||||
|
||||
```html
|
||||
<div class="btn btn-left" @click="previous">
|
||||
<ImgBtn>
|
||||
<template>
|
||||
<i class="iconfont icon-next"></i>
|
||||
</template>
|
||||
</ImgBtn>
|
||||
</div>
|
||||
<div class="btn btn-right" @click="next">
|
||||
<ImgBtn>
|
||||
<template>
|
||||
<i class="iconfont icon-next"></i>
|
||||
</template>
|
||||
</ImgBtn>
|
||||
</div>
|
||||
```
|
||||
|
||||
这里的`this.dest`是稍后用作动画过渡而定义的方向。
|
||||
|
||||
```js
|
||||
methods: {
|
||||
next() {
|
||||
this.dest = 'img-next';
|
||||
this.show == this.images.length - 1 ? (this.show = 0) : this.show++;
|
||||
},
|
||||
previous() {
|
||||
this.dest = 'img-previous';
|
||||
this.show == 0 ? (this.show = this.images.length - 1) : this.show--;
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### 小圆点
|
||||
|
||||
同样的小圆点也是封装为一个小组件,外部直接使用 ol 来嵌套。同样的,小圆点的数量由图片来决定,所以它也是遍历`v-for`images 数组。
|
||||
|
||||
而小圆点的激活样式同样的也是由变量 show 来进行驱动,当 show 等于当前下标时,则激活样式。小圆点和图片的激活都由同一个变量驱动,这样在修改这一个变量时,小圆点和图片都能正确切换到对应的序列。
|
||||
|
||||
```html
|
||||
<ol class="img-point" @click="pointClick">
|
||||
<ImgPoint v-for="(item, i) in images" :key="item.id + 'point'">
|
||||
<template>
|
||||
<i :class="{ active: show == i }" :id="i"></i>
|
||||
</template>
|
||||
</ImgPoint>
|
||||
</ol>
|
||||
```
|
||||
|
||||
小圆点这里绑定了一个 id 为数组下标,这样当点击到当前下标的圆点时,就能通过事件修改 show 的值了。这里事件同样使用事件代理。
|
||||
|
||||
这里的 if 判断是用来判断后续过渡动画的方向的。
|
||||
|
||||
```js
|
||||
pointClick(e) {
|
||||
console.log(e.target.id);
|
||||
if (e.target.nodeName.toString().toLowerCase() == 'i') {
|
||||
this.show > e.target.id
|
||||
? (this.dest = 'img-previous')
|
||||
: (this.dest = 'img-next');
|
||||
this.show = e.target.id;
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
### 过渡动画
|
||||
|
||||
这里使用了 Vue 的过渡效果,正向移动时,将即将进入的图片向右偏移一个自身宽度,然后再慢慢的过渡进当前的区域,离开的图片也是同理,慢慢的过渡到向左偏移一个自身宽度。
|
||||
|
||||

|
||||
|
||||
```css
|
||||
.img-next-enter-active,
|
||||
.img-next-leave-active,
|
||||
.img-previous-enter-active,
|
||||
.img-previous-leave-active {
|
||||
transition: all 300ms;
|
||||
}
|
||||
.img-next-enter,
|
||||
.img-previous-leave-to {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
.img-next-enter-to,
|
||||
.img-next-leave,
|
||||
.img-previous-enter-to,
|
||||
.img-previous-leave {
|
||||
transform: translateX(0);
|
||||
}
|
||||
.img-next-leave-to,
|
||||
.img-previous-enter {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
```
|
||||
|
||||
稍微复杂一点的就是左右方向移动时,对应的动画顺序是不一样的。所以这里的`<transition-group :name="dest" tag="ul" class="img">`的 name 的值是动态绑定的。当向什么方向移动时,就应用什么方向的 css。
|
||||
|
||||
上述左右按钮的点击和小圆点的点击事件里均有修改对应方向的值。
|
||||
|
||||
```js
|
||||
data() {
|
||||
return {
|
||||
dest: 'img-next',
|
||||
}
|
||||
```
|
||||
|
||||
### 自动播放
|
||||
|
||||
图片自动滚动不是什么复杂的事情,设置一个定时器就能解决。
|
||||
|
||||
```js
|
||||
autoPlay() {
|
||||
this.dest = 'img-next';
|
||||
this.timer = setInterval(() => {
|
||||
this.show == 4 ? (this.show = 0) : this.show++;
|
||||
}, 3000);
|
||||
},
|
||||
```
|
||||
|
||||
此外在绑定两个鼠标移入和移除的事件,用来设置暂停和继续播放。
|
||||
|
||||
```html
|
||||
@mouseover="pausePlay" @mouseout="continuePlay"
|
||||
```
|
||||
|
||||
```js
|
||||
pausePlay() {
|
||||
clearInterval(this.timer);
|
||||
},
|
||||
continuePlay() {
|
||||
this.autoPlay();
|
||||
}
|
||||
```
|
||||
|
||||
## 🎉
|
||||
|
||||
这里的 Vue 实现方案主要是用于 PC 端的,它可以利用 Vue 的 `transition-group`来过渡切换的图片,轻松实现无限循环。
|
||||
|
||||
但这个方案的一个缺点就是对于触屏的移动端不是很友好,单靠`transition-group`来给滑动时显示图片会比较复杂。更佳的实现方案还是将上述传统的实现方式封装成一个组件,后续再考虑实现一下。
|
BIN
source/images/某咸鱼的ToDoList实践/2021-02-02-11-40-13.webp
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
source/images/某咸鱼的ToDoList实践/2021-02-02-11-48-57.webp
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
source/images/某咸鱼的ToDoList实践/2021-02-02-14-52-48.webp
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
source/images/某咸鱼的ToDoList实践/2021-02-11-17-49-56.webp
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
source/images/某咸鱼的ToDoList实践/logo.webp
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
source/images/经典轮播图的实现方案/2021-02-02-14-52-48.webp
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
source/images/经典轮播图的实现方案/2021-02-13-15-37-51.webp
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
source/images/经典轮播图的实现方案/2021-02-13-15-39-13.webp
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
source/images/经典轮播图的实现方案/logo.webp
Normal file
After Width: | Height: | Size: 5.7 KiB |