commit 3027a7be4f3496d79fe4304b74cda199984ea74c Author: 奇趣保罗 Date: Wed Sep 18 17:07:01 2024 +0800 Init: 2023 Version 从服务器拉取的最新版本 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d64a3d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +sample diff --git a/index.html b/index.html new file mode 100755 index 0000000..9b0c4ec --- /dev/null +++ b/index.html @@ -0,0 +1,497 @@ + + + + + 开始 - Kico Player + + + + + + + + + + +
+
+
+

开始

+

如何使用 Kico Player

+
+
+
+

介绍

+

Kico Player 是一款外观简洁、功能强大的前端音乐播放器,由 奇趣保罗 开发,是 Kico 前端作品系列的一员。

+
    +
  • 最新版本:0.9
  • +
  • 上次更新:2023.5.22
  • +
  • 上次修订:2023.2.8
  • +
+
+
+

体验一番

+

这是作者保罗喜欢听的一些歌曲~

+
+

+ + + +

+

这些歌曲是缤奇成员自创的音乐~

+
+

+ + +

+

浏览器信息:

+
+
+

下载使用

+

在 GitHub 上获取

+

1. 引用 CSS 样式

+
<link href="KPlayer.css" rel="stylesheet" type="text/css" />
+

2. 引用 JS

+
<script src="KPlayer.js"></script>
+

3. 设置启动器

+
const player = new KPlayer({
+  container: document.querySelector("#player-1"),
+  playlist: [
+    {
+      title: "Born Free",
+      artist: "The Rassle",
+      album: "Introducing",
+      cover: "sample/cover/1.jpg",
+      link: "sample/music/1.mp3"
+    }
+  ]
+})
+

启动器参数及 API,详见 参数 页面

+
+
+

另请参阅

+

还有前端框架 Kico Style、时钟小工具 Kico Tools 等有趣的前端作品,欢迎关注!

+
+

奇趣框架 (Kico Style)

+

一款清爽简洁的响应式 CSS 前端框架,同样由保罗开发。

+

+ 项目地址 + 文档地址 + 项目故事 +

+
+
+

奇趣小工具 (Kico Tools)

+

一个集合了时钟、秒表、倒计时和随机数的小工具,依旧是由保罗开发。它采用 SvelteJS 框架进行开发。

+

+ 项目地址 +

+
+
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/settings.html b/settings.html new file mode 100755 index 0000000..8e4ed96 --- /dev/null +++ b/settings.html @@ -0,0 +1,237 @@ + + + + + 参数 - Kico Player + + + + + + + + + + +
+
+
+

参数

+

如何使用 Kico Player

+
+
+
+

参数类型

+

播放器初始化参数

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
参数可选值说明
containerDOM 元素元素选择器,可以是 document.getElementById("player")
playlist数组播放列表,数据类型为一个 歌曲对象
autoplaytrue false是否自动播放音乐,默认为 false
show_listtrue false是否展开播放列表,默认为 false
title_changetrue false是否在播放音乐时修改页面标题,默认为 false
+
+

歌曲对象参数

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
参数可选值说明
title音乐标题
artist音乐作者
album音乐专辑名称
cover音乐专辑封面
link音乐链接地址
+
+
+
+

开放接口

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
方法参数说明
play播放当前选择的音乐
pause暂停当前选择的音乐
toggle切换播放/暂停
jumpid播放列表的指定音乐
prev切换播放上一首音乐
next切换播放下一首音乐
mode切换当前播放模式
add包含歌曲构造 JSON 的一个数组向列表添加音乐
remove删除倒数第一首音乐
toggle_list打开或关闭播放列表
toggle_volume切换音量
+
+

示例

+

引用一个播放器,添加两首音乐,默认展开列表,并开启标题切换

+
const player = new KPlayer({
+    container: document.getElementById("player-1"),
+    playlist: [
+        {
+            title: "Born Free",
+            artist: "The Rassle",
+            album: "Introducing",
+            cover: "sample/cover/1.jpg",
+            link: "sample/music/1.mp3"
+        },
+        {
+            title: "Summer Vibe",
+            artist: "Walk off the Earth",
+            album: "Summer Vibe",
+            cover: "sample/cover/2.jpg",
+            link: "sample/music/2.mp3"
+        }
+    ],
+    show_list: true,
+    title_change: true
+});
+
+

使播放器播放和暂停

+
player.play();
+player.pause();
+
+

使播放器添加一首新音乐

+
player.add([{
+    title: "Summer Vibe",
+    artist: "Walk off the Earth",
+    album: "Summer Vibe",
+    cover: "sample/cover/2.jpg",
+    link: "sample/music/2.mp3"
+}]);
+
+

使播放器删除最后一首歌曲

+
player.remove();
+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/static/KPlayer.css b/static/KPlayer.css new file mode 100644 index 0000000..f73921c --- /dev/null +++ b/static/KPlayer.css @@ -0,0 +1,284 @@ +@charset "UTF-8"; + +/* ---- + +# Kico Player 0.9 +# By: Dreamer-Paul +# Last Update: 2021.8.30 + +一个简洁强大的网页音乐播放器。 + +本代码为奇趣保罗原创,并遵守 MIT 开源协议。欢迎访问我的博客:https://paugram.com + +---- */ + +kplayer{ + --kp-primary: #3498db; + --kp-secondly: #ffc670; + --kp-gray: #aaa; + + display: block; + overflow: auto; + font-size: 14px; + line-height: 1.5em; + background: #fff; + border-radius: 4px; + position: relative; + box-shadow: 0 1px 5px 1px rgba(0, 0, 0, .1), 0 0 5px rgba(0, 0, 0, .1); +} +kplayer *{ + margin: 0; + padding: 0; + box-sizing: border-box; +} + +/* - 面板 */ +kplayer .kp-header{ + height: 4em; + cursor: default; + overflow: hidden; + line-height: 1em; + user-select: none; + position: relative; +} + +/* - 专辑 */ +kplayer .kp-cover{ + width: 4em; + height: 4em; + float: left; + margin-right: 1em; + vertical-align: middle; + transition: background .3s; + background: var(--kp-gray) url(data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMqaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjUtYzAyMSA3OS4xNTQ5MTEsIDIwMTMvMTAvMjktMTE6NDc6MTYgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTNERjEzRjQzMDQzMTFFOEI5NkQ5NTkwMTU2NDBFMzUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTNERjEzRjUzMDQzMTFFOEI5NkQ5NTkwMTU2NDBFMzUiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1M0RGMTNGMjMwNDMxMUU4Qjk2RDk1OTAxNTY0MEUzNSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1M0RGMTNGMzMwNDMxMUU4Qjk2RDk1OTAxNTY0MEUzNSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/uACZBZG9iZQBkwAAAAAEDABUEAwYKDQAABu4AAAdjAAAJgAAAC1D/2wCEAAYEBAQFBAYFBQYJBgUGCQsIBgYICwwKCgsKCgwQDAwMDAwMEAwODxAPDgwTExQUExMcGxsbHB8fHx8fHx8fHx8BBwcHDQwNGBAQGBoVERUaHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fH//CABEIAMgAyAMBEQACEQEDEQH/xACvAAEAAwEBAQAAAAAAAAAAAAAAAgMEBQEGAQEAAAAAAAAAAAAAAAAAAAAAEAACAgEDAwQDAQAAAAAAAAABAgADERBAEjAyBCCQMSJgcBMzEQABAwIFBQEBAAAAAAAAAAABAEARECEgMVFhEnBxgZECocESAQAAAAAAAAAAAAAAAAAAAJATAQAABAQDCAIDAAAAAAAAAAEAESExEEFRYUCBkSAw8HGhscHRkOFgcPH/2gAMAwEAAhEDEQAAAfsgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADabjOc08AAAAAAAAABI7BIHHKwAAAAAAAATOgCs2lBgNxziAAAAAAAANxuBxgbTYCg5Z4AAAAAAASOyemUvJgAwmEAAAAAAAHRNYBQZikrPDwAAAAAAAGs6IOaZQAAAAAAAAADQdQicUAAAAAAAAAAGo6QOOVgAAAAAAAAAG82ggc0oAAAAAAAAAB1i4rLAVGYoKAAAAAAAATOyDlmg2AGAxngAAAAABItNRrBlKSRqImU8PCBURAAAABqJFhMFZAAAAETKAAAAD0sJkj09AAPDwiQKzwAAAAAAA9PQDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//9oACAEBAAEFAv0PTUDMCWUgjakERe3R+/ZKpY10hZeuUptGJZaFCVs8FCcWUqdh476sMEcolGttfIbAHBByJZTydUVfTfXsfHP11a5BD5DQ22GcjsvHP30ttJO1o/0j9u28fv0dOLbXxhq6Bg9TLtaRiuK6tq1SND40NLjYqMtpYpFiWt6L3J64BM4SpBnS3ugYiK4MsbGvEThCCOmPiK+B/Uz+pjNy6J+Olmc5zEyOhkTmJzmdjkzJmT+Yf//aAAgBAgABBQL2cf/aAAgBAwABBQL2cf/aAAgBAgIGPwIcf//aAAgBAwIGPwIcf//aAAgBAQEGPwLoPy+vApIzbXQqe7OApNyp0XE+Kbr+qP1QWPGpCgKfr1XcMZU0n2rYeQ8sY0waq1lms2sDJsKHs6hsTWCtRq1FN9K5KxZAVMKPr3g4jJjOLdR06//aAAgBAQMBPyH+hxLhAASCmkNhl7+GGESnaJadpGMtHVwciM2P0yxvCuJQskuwlYZ5SEpls1AFcsGN44Equ7UcEERswruTDdV8iDM1gAUMK2bX1wKALkGYs4SKaRKBMjz7PxT54Getb37FNnNoQ+w9UZh5UhW6efBSkamLp5H14b3XthMS7ze3DibdDBrSGZWyduGocrHoYYq/IcL5or1wngWXxXaR1KQ3xsFqlDOfA7sMFMNyUxi0YCJMxUyF3Xv7JAc2LStiOowZp0jYdESUl3FXKE5MXTu0WWwWUEbRG0QskyUuxOfZRdbuxFoHmQeTG/3G/D5sLyIU34CbrG4xvMbjE3X+X//aAAgBAgMBPyH8OP8A/9oACAEDAwE/Ifw4/wD/2gAMAwEAAhEDEQAAEJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIBJJJJJJJJJJBIJJJJJJJJJAJJJJJJJJJJJIIBBIBJJJJJJJAJJJIJJJJJJJJJJABBJJJJJJJIJJJJJJJJJJJJJBJJJJJJJJJJJJJJJJJJJJJJJJIJJJJJJJJJJJJJABJJJJJJJIJBBIBJJJJJJIBIBJBAJJJJJJBAIAIABJJJJJJJIAAJJJJJJJJJJIAJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ//2gAIAQEDAT8Q/oc2Uy5ZlmxIANAAgUR1AobE4ZRIM20mQQWhdMSDsodeDAJTTLziSSMoyeWsTcFToaMZOwSyaYIgKUay3Ykkk3RfuGyS8l3HbSBY0HJMk4ECANxOo4E1METZi+wxyyYRGVszEsXOktzc4AAAKAUMDluc9SCIokkucA/MlE5RaGRMFJDyLOZaXKKJOqqvm9mSp0bWvhXgZvNZh5ft2E0Se6dbRRDNWr4PSLoXgylHqYJhVvwOgr9RHGwgSnhThp+R7mCTsIec3D6YO9UMABVRokBlq1snDS25oeVX3xk9tYuO0OMnhKmXCyK3M/qPTBRUmRLkvfGcMx331FSg6DL1PqCjpTQJS9+BYG5HLOAACgUDBKCWil606weS0yMp8yACCNkxkJZ1pkvoiUu+timraC3nYpBUbMh3f1jIDoPxhPjpmrMU7mPiKp6zoYINycXA8qe0NvGzWLYpqW7to0rYSFWs1Y8Mx4ZhhAprb9hSms1uvZKucd23NSgmduUhN18x9QJY9YEbM+0oXZQhc9YDZfIfcMyt2sNzU3gA7LrH+8x/rMf7zC911/l//9oACAECAwE/EPw4/wD/2gAIAQMDAT8Q/Dj/AP/Z) center/cover; +} + +/* - 时间 */ +kplayer .kp-time{ + left: 0; + width: 4em; + opacity: 0; + color: #fff; + display: block; + line-height: 4em; + position: absolute; + text-align: center; + transition: opacity .3s; + background: rgba(0, 0, 0, .5); + border-top-left-radius: 4px; +} +kplayer .kp-header:hover .kp-time{ + opacity: 1; +} + +/* - 信息 */ +kplayer .kp-info{ + margin: .6em 6.5em .5em 5em; +} +kplayer .kico-title, kplayer .kico-artist{ + display: block; + overflow: hidden; + white-space: nowrap; + padding-bottom: .5em; + text-overflow: ellipsis; +} +kplayer .kico-artist{ + color: #aaa; + color: var(--kp-gray); + font-size: .85em; +} + +/* - 按钮 */ +kplayer .kp-controls{ + top: 0; + left: 4em; + right: 0; + bottom: 0; + opacity: 0; + padding: .5em; + background: #fff; + position: absolute; + transition: opacity .3s; +} +kplayer .kp-header:hover .kp-controls{ + opacity: 1; +} + +kplayer .control-item{ + color: #3498db; + color: var(--kp-primary); + cursor: pointer; + display: inline-block; +} +kplayer .control-item svg{ + width: 2.5em; + height: 2.5em; +} + +/* - 设置 */ +kplayer .kp-settings{ + top: .75em; + right: .5em; + position: absolute; +} + +kplayer .setting-item{ + width: 2em; + color: #aaa; + color: var(--kp-gray); + cursor: pointer; + line-height: 2em; + text-align: center; + display: inline-block; +} +kplayer .setting-item svg{ + width: 1.5em; + height: 1.5em; + vertical-align: middle; +} + +/* - 进度条 */ +kplayer .kp-bar{ + left: 4em; + right: 0; + bottom: 0; + height: .4em; + cursor: pointer; + position: absolute; + background: #efefef; +} +kplayer .kp-bar .loaded, +kplayer .kp-bar .played{ + top: 0; + height: .4em; + max-width: 100%; + position: absolute; +} +kplayer .kp-bar .loaded{ + background: #e5e5e5; +} +kplayer .kp-bar .played{ + background-color: #ffc670; + background-color: var(--kp-secondly); +} + +/* 下拉载入 +-------------------------------- */ +kplayer .kp-drop{ + top: 0; + left: 0; + right: 0; + bottom: 0; + opacity: 0; + color: #fff; + visibility: hidden; + position: absolute; + pointer-events: none; + background: rgba(0, 0, 0, .3); + transition: opacity .3s, visibility .3s; +} +kplayer .kp-drop.active{ + opacity: 1; + visibility: visible; +} +kplayer .kp-drop span{ + top: 0; + left: 0; + right: 0; + bottom: 0; + height: 1em; + margin: auto; + display: block; + font-size: 2em; + line-height: 1em; + text-align: center; + position: absolute; +} + +/* 播放列表 +-------------------------------- */ +kplayer .kp-list{ + max-height: 0; + overflow: auto; + list-style: none; + user-select: none; + transition: max-height .3s; + border-bottom: 1px solid #eee; +} +kplayer .kp-list.show{ + max-height: 15em; + max-height: 50vh; +} + +kplayer .kp-list::-webkit-scrollbar{ width: 5px } +kplayer .kp-list::-webkit-scrollbar-thumb{ + background: #eee; +} +kplayer .kp-list::-webkit-scrollbar-thumb:hover{ + background-color: #3498db; + background-color: var(--kp-primary); +} + +kplayer .list-item{ + cursor: pointer; + overflow: hidden; + padding: .75em 1em; + position: relative; + white-space: nowrap; + text-overflow: ellipsis; + transition: background .3s, padding .3s; +} +kplayer .list-item.current{ + padding-left: 1.5em; +} +kplayer .list-item.current:before{ + content: ''; + top: 1em; + left: -.5em; + width: 1em; + height: 1em; + display: block; + position: absolute; + background-color: #ffc670; + background-color: var(--kp-secondly); + transform: rotate(45deg); +} +kplayer .list-item:nth-child(1n){ + background: rgba(0, 0, 0, .02); +} +kplayer .list-item:nth-child(2n){ + background: #fff; +} +kplayer .list-item:hover{ + background: rgba(0, 0, 0, .05); +} + +kplayer .item-number, kplayer .item-artist{ + opacity: .6; +} + +kplayer .item-number{ + margin-right: .75em; +} +kplayer .item-title:after{ + content: '-'; + margin: 0 .25em; +} + +/* 歌词 +-------------------------------- */ +kplayer .kp-lyrics{ + color: #666; + text-align: center; +} +kplayer .kp-lyrics span{ + padding: 1em; + display: block; + overflow: hidden; + line-height: 1em; + white-space: nowrap; + text-overflow: ellipsis; +} +kplayer .kp-lyrics span:empty:after{ + content: "Music..."; +} diff --git a/static/KPlayer.js b/static/KPlayer.js new file mode 100644 index 0000000..e29d8e9 --- /dev/null +++ b/static/KPlayer.js @@ -0,0 +1,602 @@ +/* ---- + +# KPlayer 0.9 +# By: Dreamer-Paul +# Last Update: 2023.5.22 + +一个简洁强大的网页音乐播放器。 + +本代码为奇趣保罗原创,并遵守 MIT 开源协议。欢迎访问我的博客:https://paugram.com + +---- */ + +var KPlayer = function KPlayer (settings) { + var that = this; + + // 状态记录 + var current = { + id: 0, + last_id: 0, + playlist: [], + randlist: [], + lyric: [], + lyric_index: 0, + play_mode: 0, // 默认、循环、随机模式 normal(0) loop(1) random(2) + page_title: document.title + }; + + var icons = { + left: '', + right: '', + play: '', + pause: '', + + none: '', + low: '', + mid: '', + max: '', + + loop_all: '', + loop_single: '', + rand: '', + + list: '' + }; + + // 小工具 + var tools = { + // 拖拽文件名称识别 + name: function (filename) { + filename = filename.replace(/\.ogg|\.mp3|\.wav|\.mp4?/, ""); + var sp = filename.split(/\s\-\s|\s\–\s/); + return {title: sp[1], artist: sp[0]}; + }, + // 快捷创建对象 + create: function (tag, set) { + var obj = document.createElement(tag); + + if(!set) return obj; + + if(set.class) obj.className = set.class; + if(set.text) obj.innerText = set.text; + if(set.html) obj.innerHTML = set.html; + + return obj; + }, + // 快捷添加对象 + append: function (obj, child) { + for(var i = 0; i < child.length; i++){ + obj.appendChild(child[i]); + } + } + }; + + // 元素模块 + var elements = { + container: tools.create("kplayer"), + player: document.createElement("audio"), + buttons: { // 操作按钮 + toggle: tools.create("div", { class: "control-item kico-toggle", html: icons.play }), + prev: tools.create("div", { class: "control-item kico-prev", html: icons.left }), + next: tools.create("div", { class: "control-item kico-next", html: icons.right }), + mode: tools.create("div", { class: "setting-item kico-mode", html: icons.loop_all }), + list: tools.create("div", { class: "setting-item kico-list", html: icons.list }), + volume: tools.create("div", { class: "setting-item kico-volume", html: icons.max }) + }, + details: { // 歌曲详情 + title: tools.create("span", { class: "kico-title", text: "欢迎使用 Kico Player" }), + artist: tools.create("span", { class: "kico-artist", text: "奇趣保罗" }), + cover: tools.create("div", { class: "kp-cover" }), + time: tools.create("div", { class: "kp-time", text: "00:00" }) + }, + bar: { // 播放进度条 + wrap: tools.create("div", { class: "kp-bar" }), + loaded: tools.create("div", { class: "loaded" }), + played: tools.create("div", { class: "played" }) + }, + playlist: tools.create("div", { class: "kp-list" }), + lyric: tools.create("span", { text: "欢迎使用 Kico Player" }), + list_items: [] + }; + + // 事件 + var events = { + onPlay: () => { + if (!interval) interval = setInterval(interval_fc, 500); + + elements.buttons.toggle.innerHTML = icons.pause; + actions.title_change(true); + }, + onPause: () => { + interval = clearInterval(interval); + elements.buttons.toggle.innerHTML = icons.play; + actions.title_change(false); + }, + onProgress: () => { + const player = elements.player; + const percentage = player.buffered.length ? ( + player.buffered.end(player.buffered.length - 1) / player.duration + ) : 0; + + elements.bar.loaded.style.width = percentage * 100 + "%"; + }, + onError: () => { + interval = clearInterval(interval); + elements.buttons.toggle.innerHTML = icons.play; + actions.title_change(false); + + elements.details.title.innerText = ":("; + elements.details.artist.innerText = "发生了错误"; + }, + onEnd: () => { + // 列表和随机列表循环 + if(current.play_mode === 0 || current.play_mode === 2){ + this.next(); + } + // 单曲循环 + else if(current.play_mode === 1){ + current.lyric_index = 0; + this.play(); + elements.lyric.innerText = current.playlist[current.id].title + " (" + current.playlist[current.id].artist + ")"; + } + } + } + + var wrapper = { + wrap: tools.create("div", { class: "kp-header" }), + info: tools.create("div", { class: "kp-info" }), + ctrl: tools.create("div", { class: "kp-controls" }), + sets: tools.create("div", { class: "kp-settings" }), + lyric: tools.create("div", { class: "kp-lyrics" }), + drop: tools.create("div", { class: "kp-drop", html: "释放鼠标以添加歌曲" }) + }; + + var interval; + var interval_fc = function () { + actions.update_time(); + actions.update_bar(); + actions.update_lyric_playing(); + }; + + // 构造模块 + var build = { + elements: function () { + // 歌手歌名 + tools.append(wrapper.info, [elements.details.title, elements.details.artist]); + + // 控制按钮 + tools.append(wrapper.ctrl, [elements.buttons.prev, elements.buttons.toggle, elements.buttons.next]); + + // 设置按钮 + tools.append(wrapper.sets, [elements.buttons.volume, elements.buttons.mode, elements.buttons.list]); + + // 整体框架 + tools.append(wrapper.wrap, [elements.details.cover, elements.details.time, wrapper.info, wrapper.ctrl, wrapper.sets, elements.bar.wrap]); + + wrapper.lyric.appendChild(elements.lyric); + + tools.append(elements.container, [wrapper.wrap, elements.playlist, wrapper.lyric, wrapper.drop]); + settings.container.appendChild(elements.container); // 总添加 + + // 拖动播放 + elements.container.ondragenter = function (e) { + e.preventDefault(); + wrapper.drop.classList.add("active"); + }; + elements.container.ondragover = function (e) { + e.preventDefault(); + wrapper.drop.classList.add("active"); + }; + elements.container.ondrag = function (e) { + e.preventDefault(); + wrapper.drop.classList.add("active"); + }; + elements.container.ondragleave = function (e) { + e.preventDefault(); + wrapper.drop.classList.remove("active"); + }; + elements.container.ondrop = function (e) { + e.preventDefault(); + wrapper.drop.classList.remove("active"); + var arr = []; + + for(var i = 0; i < e.dataTransfer.items.length; i++){ + var s = e.dataTransfer.items[i].getAsFile(); + + if (window.createObjectURL) { + url = window.createObjectURL(s) + } + else if (window.createBlobURL) { + url = window.createBlobURL(s) + } + else if (window.URL && window.URL.createObjectURL) { + url = window.URL.createObjectURL(s) + } + else if (window.webkitURL && window.webkitURL.createObjectURL) { + url = window.webkitURL.createObjectURL(s) + } + + var n = tools.name(s.name); + + arr.push({ + title: n.title, + artist: n.artist, + link: url + }); + } + + that.add(arr); + that.jump(current.playlist.length - 1); + }; + + // 播放器 + elements.player.volume = 1; + + elements.player.addEventListener("play", events.onPlay); + elements.player.addEventListener("pause", events.onPause); + elements.player.addEventListener("progress", events.onProgress); + elements.player.addEventListener("error", events.onError); + elements.player.addEventListener("ended", events.onEnd); + + // 按钮们 + elements.buttons.toggle.addEventListener("click", that.toggle); + elements.buttons.prev.addEventListener("click", that.prev); + elements.buttons.next.addEventListener("click", that.next); + elements.buttons.mode.addEventListener("click", that.mode); + elements.buttons.list.addEventListener("click", that.toggle_list); + elements.buttons.volume.addEventListener("click", that.toggle_volume); + + // 进度条 + elements.bar.wrap.appendChild(elements.bar.loaded); + elements.bar.wrap.appendChild(elements.bar.played); + elements.bar.wrap.addEventListener("click", function (t) { + if (!elements.player.currentTime) return; + + elements.player.currentTime = (t.offsetX / elements.bar.wrap.clientWidth) * elements.player.duration; + actions.update_bar(); + actions.update_time(); + + current.lyric.length && actions.update_lyric(); + }); + }, + playlist: function (item) { + function add(s) { + current.playlist.push(s); + current.randlist.push(s); + + var id = current.playlist.length - 1; + var li = tools.create("div", { class: "list-item" }); + + li.innerHTML = "" + (id + 1) + + "" + current.playlist[id].title + + "" + current.playlist[id].artist + ""; + + li.onclick = function () { + if(current.id === id){ + that.toggle(); + } + else { + current.id = id; + that.jump(current.id); + } + }; + + elements.playlist.appendChild(li); + elements.list_items.push(li); + } + + item.forEach(function (t) { + add(t); + }); + }, + randlist: function () { + for(var i = 0; i < current.randlist.length - 1; i++){ + i++; + var a = calc.random(0, current.randlist.length - 1); + var b = calc.random(0, current.randlist.length - 1); + var cache = current.randlist[a]; + + current.randlist[a] = current.randlist[b]; + current.randlist[b] = cache; + } + }, + lyric: function (lyric, sub_lyric) { + if (!lyric) { + current.lyric = []; + elements.lyric.innerText = "纯音乐,请欣赏..."; + return; + } + + elements.lyric.innerText = `${current.playlist[current.id].title} (${current.playlist[current.id].artist})`; + + var time, text, sub_text; + + // var text = lyric.match(/\[[0-9]{2,}:[0-9]{2}\.[0-9]{2,}\](\S| )+/g).replace(/\[[0-9]{2,}:[0-9]{2}\.[0-9]{2,}\]/g); + + time = lyric.match(/\d{2,}:\d{2,}.\d{1,4}/g); + text = lyric.match(/\d{1}\]+.*/g); + + if(sub_lyric) sub_text = sub_lyric.match(/\d{1}\]+.*/g); + + if(settings.debug) console.log(time, text, sub_text); + + // 时间和歌词数量解析结果对的上 + if (time && (time.length === text.length)) { + current.lyric = time.map((item, index) => { + const _range = item.match(/\d+/g); + const min = parseInt(_range[0] * 60); + const sec = parseInt(_range[1]); + const ms = parseFloat(_range[2].substr(0, 2) / 60); + + const _lyric = { + time: min + sec + ms, + text: text[index].substr(2) + } + + if (sub_text && text.length === sub_text.length) { + _lyric.sub_text = sub_text[index].substr(2); + } + + return _lyric; + }); + + if(settings.debug) console.log(current.lyric); + } + else { + current.lyric = [{ + time: 0, + text: "当前歌词不支持滚动" + }]; + } + } + }; + + // 播放与暂停切换 + this.play = function () { + elements.player.src && elements.player.play(); + }; + this.pause = function () { + elements.player.src && elements.player.pause(); + }; + this.toggle = function () { + if(elements.player.src){ + elements.player.paused ? elements.player.play() : elements.player.pause(); + } + else{ + that.jump(current.id); + } + }; + + this.jump = function (id) { + if(id === undefined) return; + + // 更新信息 + current.id = id; + current.lyric_index = 0; + + elements.details.title.innerText = current.playlist[current.id].title; + elements.details.artist.innerText = current.playlist[current.id].artist; + elements.details.cover.style.backgroundImage = current.playlist[current.id].cover ? "url('" + current.playlist[current.id].cover + "')" : ""; + elements.player.src = current.playlist[current.id].link; + elements.player.play(); + + if("mediaSession" in navigator){ + var _meta = { + title: current.playlist[current.id].title, + artist: current.playlist[current.id].artist, + }; + + if(current.playlist[current.id].album) _meta.album = current.playlist[current.id].album; + if(current.playlist[current.id].cover) _meta.artwork = [{ src: current.playlist[current.id].cover }] + + navigator.mediaSession.metadata = new MediaMetadata(_meta); + navigator.mediaSession.setActionHandler("play", that.play); + navigator.mediaSession.setActionHandler("pause", that.pause); + navigator.mediaSession.setActionHandler("previoustrack", that.prev); + navigator.mediaSession.setActionHandler("nexttrack", that.next); + } + + // 上次播放记录 + if(elements.list_items[current.last_id]) elements.list_items[current.last_id].classList.remove("current"); + elements.list_items[current.id].classList.add("current"); + current.last_id = current.id; + + // 如果有歌词则处理歌词 + build.lyric(current.playlist[current.id].lyric, current.playlist[current.id].sub_lyric); + + if(settings.debug) console.log("当前 ID:" + current.id); + }; + + // 上一首 + this.prev = function () { + if(current.play_mode === 0 || current.play_mode === 1) { + current.id > 0 ? current.id-- : current.id = current.playlist.length - 1; + that.jump(current.id); + } + else if(current.play_mode === 2){ + var a = current.randlist.indexOf(current.playlist[current.id]); + a === 0 ? current.id = current.randlist.length - 1 : current.id = current.playlist.indexOf(current.randlist[a - 1]); + that.jump(current.id); + } + }; + + // 下一首 + this.next = function () { + current.lyric_index = 0; + + if(current.play_mode === 0 || current.play_mode === 1){ + current.id < current.playlist.length-1 ? current.id++ : current.id = 0; + that.jump(current.id); + } + else if(current.play_mode === 2){ + var a = current.randlist.indexOf(current.playlist[current.id]); + a === current.randlist.length - 1 ? current.id = current.playlist.indexOf(current.randlist[0]) : current.id = current.playlist.indexOf(current.randlist[a + 1]); + that.jump(current.id); + } + }; + + // 播放模式 + this.mode = function () { + var btn = elements.buttons.mode; + + switch (current.play_mode){ + case 0: current.play_mode = 1; btn.innerHTML = icons.loop_single; break; + case 1: current.play_mode = 2; btn.innerHTML = icons.rand; build.randlist(); break; + case 2: current.play_mode = 0; btn.innerHTML = icons.loop_all; break; + } + + if(settings.debug) console.log("当前播放模式:" + current.play_mode); + }; + + // 添加歌曲 + this.add = function (item) { + build.playlist(item); + }; + + // 移除歌曲 + this.remove = function () { + if(current.playlist.length > 0){ + if(current.randlist){ + var del = current.randlist.indexOf(current.playlist[current.playlist.length - 1]); + current.randlist.splice(del, 1); + } + elements.playlist.removeChild(elements.list_items[elements.list_items.length - 1]); + current.playlist.pop(); + elements.list_items.pop(); + } + }; + + // 播放列表切换显示 + this.toggle_list = function () { + elements.playlist.classList.toggle("show"); + }; + + // 切换音量 + this.toggle_volume = function () { + var btn = elements.buttons.volume; + var player = elements.player; + + switch (player.volume){ + case 1: player.volume = 0.75; btn.innerHTML = icons.mid; break; + case 0.75: player.volume = 0.50; btn.innerHTML = icons.low; break; + case 0.50: player.volume = 0.25; btn.innerHTML = icons.none; break; + case 0.25: player.volume = 1; btn.innerHTML = icons.max; break; + } + }; + + // 卸载播放器 + this.destroy = function () { + events.onPause(); + elements.player.pause(); + + elements.player.removeEventListener("play", events.onPlay); + elements.player.removeEventListener("pause", events.onPause); + elements.player.removeEventListener("progress", events.onProgress); + elements.player.removeEventListener("error", events.onError); + elements.player.removeEventListener("ended", events.onEnd); + + elements.buttons.toggle.removeEventListener("click", that.toggle); + elements.buttons.prev.removeEventListener("click", that.prev); + elements.buttons.next.removeEventListener("click", that.next); + elements.buttons.mode.removeEventListener("click", that.mode); + elements.buttons.list.removeEventListener("click", that.toggle_list); + elements.buttons.volume.removeEventListener("click", that.toggle_volume); + + elements.player = undefined; + elements.buttons = undefined; + + elements.container.remove(); + } + + // 事件模块 + var actions = { + // 拖拽进度条更新歌词 + update_lyric: function () { + var num = 0; + + current.lyric.forEach(function (t, index) { + if(elements.player.currentTime > t.time){ + num = index; + return false; + } + }); + + current.lyric_index = num; + + if(current.lyric[num].sub_text){ + elements.lyric.innerHTML = current.lyric[num].text.toString() + "

" + current.lyric[num].sub_text.toString(); + } + else{ + elements.lyric.innerText = current.lyric[num].text.toString(); + } + }, + // 播放过程中更新歌词 + update_lyric_playing: function () { + if(!current.lyric.length) return; + + if(current.lyric[current.lyric_index] && elements.player.currentTime > current.lyric[current.lyric_index].time){ + elements.lyric.innerText = current.lyric[current.lyric_index].text; + if(current.lyric[current.lyric_index].sub_text){ + elements.lyric.innerHTML = current.lyric[current.lyric_index].text + "

" + current.lyric[current.lyric_index].sub_text; + } + else{ + elements.lyric.innerText = current.lyric[current.lyric_index].text; + } + current.lyric_index++; + } + }, + update_time: function () { + elements.details.time.innerText = calc.sec_time(elements.player.currentTime); + }, + update_bar: function () { + elements.bar.played.style.width = (elements.player.currentTime / elements.player.duration) * 100 + "%"; + }, + title_change: function (stat) { + settings.title_change && current.playlist[current.id] && stat === true ? document.title = "▶ " + current.playlist[current.id].title + " - " + current.page_title : document.title = current.page_title; + } + }; + + // 计算函数 + var calc = { + add_zero: function (num) { + return num < 10 ? "0" + num : num; + }, + sec_time: function (second) { + if(isNaN(second)){ + return "00:00"; + } + else{ + var min = parseInt(second / 60); + var sec = parseInt(second - min * 60); + var hours = parseInt(min / 60); + var minAdjust = parseInt(second / 60 - 60 * parseInt(second / 60 / 60)); + + return second >= 3600 ? `${calc.add_zero(hours)}:${calc.add_zero(minAdjust)}:${calc.add_zero(sec)}` : `${calc.add_zero(min)}:${calc.add_zero(sec)}`; + } + }, + random: function (min, max) { + return Math.floor(Math.random() * (max - min) + min); + } + }; + + // 执行函数 + (function () { + build.elements(); + build.playlist(settings.playlist); + + if(settings.show_list === true){ + that.toggle_list(); + } + if(settings.debug){ + console.log("MP3 兼容情况:" + elements.player.canPlayType("audio/mp3")); + console.log("OGG 兼容情况:" + elements.player.canPlayType("audio/ogg")); + console.log("WAV 兼容情况:" + elements.player.canPlayType("audio/wav")); + } + + var user_agent = navigator.userAgent.toLowerCase().match(/mobile/g); + + if(settings.autoplay === true && user_agent === null) that.jump(current.id); + })(); +}; + +console.log("%c Kico Player %c https://paugram.com ","color: #fff; margin: 1em 0; padding: 5px 0; background: #3498db;","margin: 1em 0; padding: 5px 0; background: #efefef;"); diff --git a/static/doc.css b/static/doc.css new file mode 100755 index 0000000..f934659 --- /dev/null +++ b/static/doc.css @@ -0,0 +1,327 @@ +/* ---- + +# Kico Player Docs +# By: Dreamer-Paul +# Last Update: 2021.9.5 + +---- */ + +/* 0 - 全局 +-------------------------------- */ +:root{ + --light-blue: #57c4fd; + --darker-gray: #666; +} + +body{ + margin-left: 3.5em; +} + +body.has-bar{ + margin-left: 15.5em; +} + +@media screen and (max-width: 600px){ + body, body.has-bar{ + margin-top: 3.5em; + margin-left: 0; + } +} + +h1, h2, h3{ font-weight: lighter } +h4, h5, h6{ font-weight: normal } + +.btn.customed{ border-radius: 3em } + +.btn.blue.customed{ + background: #57c4fd; + box-shadow: 0 4px #41a5cd; + transition: background .3s, box-shadow .3s, transform .3s; +} +.btn.blue.customed:active{ + box-shadow: none; + background: #41a5cd; + transform: translateY(4px); +} + +.mb-2{ margin-bottom: 2em !important } + +/* 1 - 页眉 +-------------------------------- */ +.sidebar{ + top: 0; + left: 0; + bottom: 0; + z-index: 3; + color: #fff; + position: fixed; + background-color: #57c4fd; + background-color: var(--light-blue); +} + +.sidebar img, .sidebar svg{ + vertical-align: top; +} + +.sidebar .header{ + padding: 1em; + cursor: pointer; + background: #3fb2ed; +} + +.sidebar .header img{ + width: 1.5em; +} + +.sidebar .title{ + display: none; + margin-left: .25em; +} + +.sidebar .menu a{ + color: #fff; + padding: 1em; + display: block; + position: relative; +} +.sidebar .menu a:hover svg{ + transform: scale(1.1) +} +.sidebar .menu svg{ + fill: #fff; + width: 1.5em; +} + +.sidebar .menu a:before, .sidebar .menu a:after{ + opacity: 0; + visibility: hidden; + pointer-events: none; +} +.sidebar .menu a:before{ + left: 3em; + top: 1.25em; + content: ''; + position: absolute; + border: .5em solid transparent; + border-right-color: rgba(0, 0, 0, .6); +} +.sidebar .menu a:after{ + top: .5em; + left: 4em; + padding: .5em 1em; + position: absolute; + white-space: nowrap; + border-radius: .5em; + content: attr(content); + background: rgba(0, 0, 0, .6); +} + +.sidebar .menu a:hover:before, .sidebar .menu a:hover:after{ + opacity: 1; + visibility: visible; +} + +@media screen and (max-width: 600px){ + .sidebar{ + right: 0; + bottom: auto; + } + + .sidebar .header{ float: left } + .sidebar .title{ display: inline-block } + .sidebar .menu{ float: right } + .sidebar .menu a{ float: left } + + .sidebar .menu a:before{ + top: 3em; + left: 1.25em; + border-color: transparent; + border-bottom-color: rgba(0, 0, 0, .6); + } + .sidebar .menu a:after{ + top: 4em; + left: 0; + } +} + +.sub-sidebar{ + top: 0; + left: 3.5em; + bottom: 0; + width: 12em; + padding: 1em; + z-index: 1; + position: fixed; + overflow-y: auto; + border-right: 1px solid #eee; +} +.sub-sidebar > ul{ + margin: 0; + list-style: none; +} +.sub-sidebar > ul > li{ + margin-bottom: 2em; +} + +.sub-sidebar > ul > li > ul a{ + color: #555; + font-weight: lighter; +} + +.sub-sidebar a{ + color: inherit; + line-height: 2em; +} + +@media screen and (max-width: 600px){ + .sub-sidebar{ + left: -12em; + top: 3.5em; + background: #fff; + border-right: none; + transition: transform .3s; + } + .sub-sidebar.active{ + outline: 50em solid rgba(0, 0, 0, .5); + transform: translateX(100%); + } +} + +/* 2 - 主体内容 +-------------------------------- */ +main .ks-title{ + padding: 1em; + text-align: center; + margin-bottom: 2em; + border-bottom: 1px solid #eee; +} + +main .ks-title h2{ color: #666; font-size: 1.25em } + +main .ks-content{ + margin-bottom: 2em; + padding-bottom: 1em; +} + +main .ks-content section{ margin-bottom: 5em } +main .ks-content section:last-child{ margin-bottom: 0 } + +main .ks-content section > *{ margin-bottom: 1em } +main .ks-content section > *:last-child{ margin-bottom: 0 } + +main .ks-content h1{ + color: #666; + color: var(--darker-gray); + font-size: 1.5em; + position: relative; + margin-bottom: 1.5em; + padding-bottom: .5em; + display: inline-block; + scroll-margin-top: 5em; +} +main .ks-content h1:before{ + content: ""; + left: 0; + bottom: 0; + z-index: -1; + width: 1.5em; + position: absolute; + border-radius: 1em; + border-bottom: .25em solid var(--yellow); +} + +main .ks-content h2:before{ + content: "#"; + color: var(--light-blue); + margin-right: .5em; +} + +main .ks-content iframe{ + width: 100%; + height: 15em; +} + +main .more-product{ + padding: 1.5em; + border-radius: 1em; + background: #fafafa; +} +main .more-product + .more-product{ margin-top: 1em } + +main .more-product h4{ + color: #666; + color: var(--darker-gray); +} + +.show-notice .ks-notice{ + animation: none; + margin: 0 0 1em 0; +} + +.doc-show-column{ + color: #fff; + padding: 1em; + background: #3fb2ed; + border-radius: .5em; +} + + +/* 3 - 页尾 +-------------------------------- */ +footer{ + color: #666; + padding: 1em 0; + text-align: center; + border-top: 1px solid #eee; +} + +/* 返回页首 */ +footer .to-top{ + z-index: 1; + color: #fff; + width: 2em; + right: 1em; + bottom: -2em; + display: block; + position: fixed; + border-radius: 50%; + text-align: center; + background: rgba(0, 0, 0, .2); + transition: background .3s, bottom .3s; +} +footer .to-top.active{ + bottom: .85em; +} +footer .to-top:hover { + color: #fff; + background: #ffc670; +} +footer .to-top:before{ + content: "\f106"; + font: 1em/2em "FontAwesome"; +} + + +/* 4 - 附加 +-------------------------------- */ +.gt-container .gt-meta{ z-index: auto !important } +.gt-comment-text, .gt-header-controls-tip{ display: none } + +.token.comment, .token.block-comment, .token.prolog, .token.doctype, .token.cdata{ color: slategray } +.token.punctuation{ color: #f8f8f2 } +.token.namespace { opacity: .7 } +.token.property, .token.tag, .token.function-name, .token.constant, .token.symbol, .token.deleted{ color: #f92672 } +.token.boolean, .token.number { color: #ae81ff } +.token.selector, .token.attr-name, .token.string, .token.char, .token.function, .token.builtin, .token.inserted{ color: #a6e22e } + +.token.operator, .token.entity, .token.url, .token.variable{ color: #f8f8f2 } +.token.atrule, .token.attr-value, .token.class-name{ color: #e6db74 } +.token.keyword { color: #66d9ef } +.token.regex, .token.important{ color: #fd971f } + +.language-css .token.string, .style .token.string { color: #28b9be } + +.token.important, .token.bold { font-weight: bold } +.token.italic { font-style: italic } +.token.entity { cursor: help } +.token.important { font-weight: normal } diff --git a/static/doc.js b/static/doc.js new file mode 100644 index 0000000..bc4ceea --- /dev/null +++ b/static/doc.js @@ -0,0 +1,13 @@ +/* ---- + +# Kico Player Docs +# By: Dreamer-Paul +# Last Update: 2021.9.5 + +---- */ + +var app = function () { + +} + +app(); diff --git a/static/icon-192.png b/static/icon-192.png new file mode 100755 index 0000000..adea98d Binary files /dev/null and b/static/icon-192.png differ diff --git a/static/icon-32.png b/static/icon-32.png new file mode 100755 index 0000000..4bd037d Binary files /dev/null and b/static/icon-32.png differ diff --git a/static/kico.css b/static/kico.css new file mode 100755 index 0000000..bd99882 --- /dev/null +++ b/static/kico.css @@ -0,0 +1,1429 @@ +@charset "UTF-8"; + +/* ---- + +# Kico Style 1.0 +# By: Dreamer-Paul +# Last Update: 2021.8.23 + +一个可口的极简响应式前端框架。 + +本代码为奇趣保罗原创,并遵守 MIT 开源协议。欢迎访问我的博客:https://paugram.com + +---- */ + +/* 0 - 全局 +-------------------------------- */ +html, body, +dl, dt, dd, ol, ul, +h1, h2, h3, h4, h5, h6, +pre, code, form, p, +fieldset, legend, figure{ + margin: 0; + padding: 0; +} + +*, *:before, *:after{ box-sizing: border-box } + +article, aside, figcaption, figure, footer, header, hgroup, main, nav, section{ display: block } + +html{ + color: #353535; + scroll-behavior: smooth; + font: 16px/1.5 'Microsoft Yahei', 'PingFang SC', 'Hiragino Sans GB', sans-serif; +} + +html.font-s{ font-size: 14px } +html.font-m{ font-size: 16px } +html.font-l{ font-size: 18px } + +:root{ + /* 内设颜色 */ + --red: #ea644a; + --yellow: #ffb03a; + --blue: #3498db; + --green: #27a17e; + + --gray: #ccc; + --light-gray: #ddd; + --lighter-gray: #eee; + + --primary: var(--blue); /* 主色 */ + --secondly: var(--yellow); /* 次色 */ +} + +@media screen and (max-width: 768px){ + html.font-auto{ font-size: 14px } +} + +@media screen and (min-width: 1921px){ + html.font-auto{ font-size: 18px } +} + +/* - 选择内容 */ +::-moz-selection{ + color: #fff; + background-color: rgba(0, 0, 0, .66); +} + +::selection{ + color: #fff; + background-color: rgba(0, 0, 0, .66); +} + +/* - 滚动条 */ +::-webkit-scrollbar{ + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-thumb{ + background: #ccc; + background: var(--gray); + border-radius: 5px; +} +::-webkit-scrollbar-track{ border-radius: 5px } +::-webkit-scrollbar-track:hover{ background: rgba(0, 0, 0, .05) } + +body::-webkit-scrollbar-track{ border-radius: 0 } + +/* - 辅助类 */ +.float-none{ float: none !important } +.float-left{ float: left !important } +.float-right{ float: right !important } + +.clearfix:after{ + content: ''; + clear: both; + display: block; +} + +.text-left{ text-align: left !important } +.text-right{ text-align: right !important } +.text-center{ text-align: center !important } +.text-justify{ text-align: justify !important } + +.text-break{ word-break: break-all !important } +.text-nowrap{ white-space: nowrap !important } + +/* 1 - 容器 +-------------------------------- */ +.wrap{ + margin: 0 auto; + max-width: 75em; + padding: 0 1.25em; + box-sizing: content-box; +} + +.wrap.min{ max-width: 50em } +.wrap.mid{ max-width: 65em } +.wrap.max{ max-width: 85em } +.wrap.full{ max-width: 100% } + +.wrap.thin{ padding: 0 .75em } +.wrap.thick{ padding: 0 1.5em } +.wrap.clear{ + padding-left: 0; + padding-right: 0; +} + +/* 2 - 元素 +-------------------------------- */ +h1{ font-size: 2em } + +h1, h2, h3, h4, h5, h6{ margin-bottom: 1rem } + +h1:last-child, h2:last-child, h3:last-child, h4:last-child, h5:last-child, h6:last-child, p:last-child{ margin-bottom: 0 } + +p{ + line-height: 1.8; + margin-bottom: 1em; +} + +a{ + color: #3498db; + color: var(--primary); + text-decoration: none; +} + +a:hover{ + color: #ffc670; + color: var(--secondly); +} + +abbr[title]{ + cursor: help; + text-decoration: none; + border-bottom: 1px dotted; +} + +em, mark, kbd{ + font-size: .85em; + padding: .25em .5em; + border-radius: .5em; +} + +em{ + color: #fff; + font-style: normal; + background-color: #3498db; + background-color: var(--primary); +} +em.red{ background: var(--red) } +em.yellow{ background: var(--yellow) } +em.blue{ background: var(--blue) } +em.green{ background: var(--green) } + +kbd{ + color: #fff; + background: #333; + font-family: 'Consolas', 'Courier New', monospace, "微软雅黑"; +} + +img, svg, audio, video, iframe{ + max-width: 100%; + vertical-align: middle; +} + +audio, video{ outline: none } + +/* - 文章 */ +article{ + letter-spacing: .03em; +} + +article a{ + word-break: break-all; +} + +article > *{ margin-bottom: 1em } +article > *:last-child{ margin-bottom: 0 } + +article h1, article h2, article h3{ font-size: 1.2em } +article h4, article h5, article h6{ font-size: 1.1em } + +article ul, article ol, article dl{ line-height: 1.8 } + +/* - 按钮 */ +button{ + outline: 0; + font: inherit; +} + +.btn{ + color: inherit; + cursor: pointer; + background: #fff; + padding: .5em 1em; + border-radius: 4px; + display: inline-block; + border: 1px solid transparent; +} +.btn:hover{ color: inherit } + +/* -- 禁用的按钮 */ +.btn[disabled]{ + opacity: .5; + cursor: not-allowed; +} + +/* -- 按钮尺寸 */ +.btn.small{ font-size: .85em } +.btn.middle, .btn.large{ padding: .75em 1.5em } +.btn.large{ font-size: 1.2em } + +/* -- 按钮颜色 */ +.btn.red, .btn.yellow, .btn.blue, .btn.green{ color: #fff } + +.btn.red{ + background: #ea644a; + background: var(--red); +} +.btn.yellow{ + background: #ffb03a; + background: var(--yellow); +} +.btn.blue{ + background: #3498db; + background: var(--blue); +} +.btn.green{ + background: #27a17e; + background: var(--green); +} +.btn.transparent{ background: transparent } + +/* - 代码 */ +pre, code{ font-family: 'Consolas', 'Courier New', monospace } + +:not(pre) > code{ + color: #c40b00; + font-size: 85%; + word-wrap: normal; + border-radius: .5em; + padding: .25em .5em; + word-break: break-all; + background-color: #f7f2f4; +} + +pre > code{ + color: #fff; + tab-size: 4; + padding: 1em; + display: block; + overflow-x: auto; + word-break: normal; + font-size: inherit; + border-radius: 5px; + background-color: #333; +} + +/* - 项目列表 */ +ul, ol{ margin-left: 1.25em } +ul.clear, ol.clear{ + margin-left: 0; + list-style: none; +} + +dl dd{ margin-left: 1.5em } +dl dd:before{ + content: "--"; + margin-right: .25em; +} + +/* - 补间动画 */ +a, .btn{ + transition: color .3s, background .3s; + -o-transition: color .3s, background .3s; + -moz-transition: color .3s, background .3s; + -webkit-transition: color .3s, background .3s; +} + +/* - 引用*/ +blockquote{ + margin: 0 0 1em; + line-height: 1.8; + font-style: oblique; + background: #f5fafd; + padding: 1em 1em 1em 2em; + border-left: 5px #3498db solid; +} + +cite{ + color: #3498db; + color: var(--primary); + font-style: normal; +} + +/* - 分割线 */ +hr{ + border: 0; + margin: 1.5em 0; + border-top: 1px #ccc solid; +} + +/* - 表单 */ +input[disabled], textarea[disabled]{ + cursor: no-drop !important; +} + +input, select, textarea{ + outline: none; + font: inherit; + max-width: 100%; + background: none; + vertical-align: middle; +} + +input[type*="date"], input[type="email"], input[type="month"], input[type="number"], input[type="password"], input[type="search"], input[type="tel"], input[type="text"], input[type="time"], input[type="url"], input[type="week"], +select, textarea{ + padding: .5em; + color: inherit; + border-radius: 4px; + border: #ccc 1px solid; + border-color: var(--gray); + min-height: calc(2.5em + 2px); +} + +input.invalid, input:out-of-range{ + border-color: #c40b00; + background: rgba(255, 0, 0, .1); +} + +::-webkit-file-upload-button{ + color: #fff; + border: none; + outline: none; + display: block; + padding: .5em 1em; + background: #3498db; + border-radius: .5em; +} + +input[type="range"]{ + margin: 0; + height: 100%; + -webkit-appearance: none; + -moz-appearance: none; + cursor: ew-resize; + cursor: grab; + overflow: hidden; + min-height: 1.5rem; +} + +input[type="range"]:focus{ + box-shadow: none; + outline: none; +} + +input[type="range"]:active::-webkit-slider-thumb{ + border-color: #3498db; + background-color: #3498db; +} + +input[type="range"]:active::-moz-range-thumb{ + border-color: #3498db; + background-color: #3498db; +} + +input[type="range"]:focus::-ms-thumb{ + border-color: #9C27B0; + background-color: #673AB7; +} + +input[type="range"]::-moz-focus-outer{ border: 0 } + +input[type="range"]::-webkit-slider-runnable-track{ + background: #3498db; + content: ''; + height: 2px; + pointer-events: none; +} + +input[type="range"]::-webkit-slider-thumb{ + width: 14px; + height: 14px; + -webkit-appearance: none; + appearance: none; + background: #fff; + border-radius: 50px; + margin-top: -6px; + border: 1px solid rgba(0, 0, 0, .15); + transition: .3s border-color, .3s background-color; +} + +input[type="range"]::-moz-range-track{ + width: 240px; + height: 2px; + background: rgba(0, 50, 126, .12); +} + +input[type="range"]::-moz-range-thumb{ + width: 14px; + height: 14px; + background: #fff; + border-radius: 50px; + border: 1px solid rgba(0, 30, 75, .12); + position: relative; + transition: .3s border-color, .3s background-color; +} + +input[type="range"]::-moz-range-progress{ + height: 2px; + background: #467fcf; + border: 0; + margin-top: 0; +} + +textarea{ + display: block; + overflow: auto; + resize: vertical; +} + +progress{ + overflow: auto; + border-radius: 50px; +} + +progress::-webkit-progress-bar{ + background-color: #eee; +} + +/* - 表单模组 */ + +/* -- 单选多选框 */ +input[type="checkbox"], input[type="radio"]{ + float: left; + width: 1.5em; + height: 1.5em; + cursor: pointer; + position: relative; + margin: 0 .5em 0 0; + -moz-appearance: none; + -webkit-appearance: none; +} + +input[type="checkbox"]:before, input[type="radio"]:before{ + content: ''; + width: 100%; + height: 100%; + display: block; + border-radius: .2em; + box-shadow: 0 0 0 1px #ccc inset; + box-shadow: 0 0 0 1px var(--gray) inset; + transition: background .3s, box-shadow .3s; +} + +input[type="checkbox"]:after{ + top: 10%; + left: 10%; + width: 30%; + height: 60%; + content: ''; + position: absolute; + transition: transform .3s; + transform-origin: 100% 100%; + border-right: .15em solid #fff; + border-bottom: .15em solid #fff; + transform: rotate(45deg) scale(0); +} + +input[type="radio"], input[type="radio"]:before{ border-radius: 100% } +input[type="checkbox"], input[type="checkbox"]:before{ border-radius: .2em } + +input[type="radio"]:checked:before{ + background: #3498db; + background: var(--primary); + border: 2px solid #3498db; + border-color: var(--primary); + box-shadow: 0 0 0 .2em #fff inset; +} + +input[type="checkbox"]:checked:before{ + box-shadow: none; + background: #3498db; + background: var(--primary); +} + +input[type="checkbox"]:checked:after{ + transform: rotate(45deg) scale(1); +} + +/* -- 开关按钮 */ +input[type="checkbox"].switch{ + width: 4em; + height: 2em; + float: none; + cursor: pointer; + background: #eee; + position: relative; + border-radius: 50px; + border: 1px solid #ccc; + border-color: var(--gray); + box-sizing: content-box; + transition: border .3s, background .3s; +} + +input[type="checkbox"].switch:before{ + margin: 0; + border: 0; + width: 2em; + height: 2em; + content: ''; + display: block; + box-shadow: none; + background: #fff; + position: absolute; + border-radius: 100%; + transition: transform .3s; +} + +input[type="checkbox"].switch:after{ content: normal } + +input[type="checkbox"].switch:checked{ + box-shadow: none; + background: #3498db; + background: var(--primary); + border-color: #3498db; + border-color: var(--primary); +} + +input.switch:checked:before{ + background: #fff; + transform: translateX(2em); +} + +/* - 表单小组 */ +fieldset{ + border: none; + margin-bottom: 2em; +} + +fieldset > *{ margin-bottom: 1em } +fieldset:last-child{ margin-bottom: 0 } + +fieldset legend{ margin: 0 0 1em } + +fieldset input:not([type="checkbox"]):not([type="radio"]), fieldset select, fieldset textarea{ width: 100% } + +/* -- 控件模块 */ +fieldset label{ + display: block; + user-select: none; + margin-bottom: 1em; +} + +/* -- 控件标题 */ +fieldset label > span:first-child{ + font-size: .85em; + white-space: nowrap; + margin-bottom: .5rem; + display: inline-block; + color: rgba(0, 0, 0, .6); +} + +fieldset label.required > span:first-child:after{ + color: red; + content: "*"; + margin-left: .25em; +} + +/* -- 单行组合 */ +fieldset .has-group{ display: flex } + +fieldset .has-group input:not([type="checkbox"]):not([type="radio"]){ + width: auto; + flex: 1 1 auto; + border-right: none; +} + +fieldset .has-group .btn{ + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +/* -- 单行表单 */ +form .inline label, fieldset.inline label{ + display: inline-block; + vertical-align: bottom; + margin: 0 .75em .75em 0; +} + +/* - 表格 */ +.ks-table{ + width: 100%; + overflow-x: auto; + overflow-y: hidden; + border-radius: 5px; +} + +table{ + border: 0; + width: 100%; + max-width: 100%; + caption-side: bottom; + border-collapse: collapse; +} + +th:not([align]){ + text-align: inherit; + text-align: -webkit-match-parent; +} + +th, td{ padding: .75em } + +table thead tr{ + border-bottom: 2px solid #ccc; + border-bottom-color: var(--gray); +} +table tbody tr{ + border-bottom: 1px solid #ddd; + border-bottom-color: var(--light-gray); + transition: border-color .3s, background .3s; +} +table tbody tr:last-child{ border-bottom: 0 } + +table tbody tr:hover{ background: #eee } + +/* - 蓝色风格 */ +table.fill{ + border-left: 1px solid transparent; + border-right: 1px solid transparent; +} + +table.fill thead{ + background-color: #3498db; + background-color: var(--primary); + border: 1px solid #3498db; + border-color: var(--primary); + border-bottom: none; +} +table.fill thead tr{ + border-bottom: none; +} +table.fill thead th, table.fill thead td{ + color: #fff; +} + +table.fill tbody{ + border-left: 1px solid #ddd; + border-right: 1px solid #ddd; + border-bottom: 1px solid #ddd; +} +table.fill tbody tr:nth-child(even) th, table.fill tbody tr:nth-child(even){ + background: #f7f7f7; +} + +/* - 嵌入式网页 */ +iframe{ + border: none; +} + +/* - 栅格系统 */ +.row{ + display: flex; + flex-wrap: wrap; + margin: 0 -1em -2em; +} + +.row.s{ margin: 0 -.5em -1em } +.row.m{ margin: 0 -1.25em -2.5em } +.row.l{ margin: 0 -1.5em -3em } +.row.full{ margin: 0 } +.row.clear{ margin-bottom: 0 } + +.row.scrollable{ + overflow: auto; + margin-left: 0; + margin-right: 0; + flex-wrap: nowrap; +} + +/* -- 对齐方式 */ +.row.right{ justify-content: flex-end; } +.row.center{ justify-content: center; } +.row.around{ justify-content: space-around; } +.row.between{ justify-content: space-between; } + +/* -- 网格间距 */ +.row > [class*="col-"]{ + flex: 0 0 100%; + padding: 0 1em; + max-width: 100%; + margin-bottom: 2em; +} + +.row.s > [class*="col-"]{ + padding: 0 .5em; + margin-bottom: 1em; +} +.row.m > [class*="col-"]{ + padding: 0 1.25em; + margin-bottom: 2.5em; +} +.row.l > [class*="col-"]{ + padding: 0 1.5em; + margin-bottom: 3em; +} +.row.full > [class*="col-"]{ + padding: 0; + margin-bottom: 0; +} + +/* -- 自适应间距栅格 */ +@media screen and (max-width: 600px){ + .row.auto{ margin: 0 -.5em -1em } + + .row.auto > [class*="col-"]{ + padding: 0 .5em; + margin-bottom: 1em; + } +} + +@media screen and (min-width: 1920px){ + .row.auto{ margin: 0 -1.25em -2.5em } + + .row.auto > [class*="col-"]{ + padding: 0 1.25em; + margin-bottom: 2.5em; + } +} + +/* -- 栅格主体 */ +.row .col-auto{ + flex: 1 1 auto; + max-width: 100%; +} + +.row .col-1{ + flex: 0 0 8.3333%; + max-width: 8.3333%; +} + +.row .col-2{ + flex: 0 0 16.6666%; + max-width: 16.6666%; +} + +.row .col-3{ + flex: 0 0 25%; + max-width: 25%; +} + +.row .col-4{ + flex: 0 0 33.3333%; + max-width: 33.3333%; +} + +.row .col-5{ + flex: 0 0 41.6666%; + max-width: 41.6666%; +} + +.row .col-6{ + flex: 0 0 50%; + max-width: 50%; +} + +.row .col-7{ + flex: 0 0 58.3333%; + max-width: 58.3333%; +} + +.row .col-8{ + flex: 0 0 66.6666%; + max-width: 66.6666%; +} + +.row .col-9{ + flex: 0 0 75%; + max-width: 75%; +} + +.row .col-10{ + flex: 0 0 83.3333%; + max-width: 83.3333%; +} + +.row .col-11{ + flex: 0 0 91.6666%; + max-width: 91.6666%; +} + +.row .col-12{ + flex: 0 0 100%; + max-width: 100%; +} + +/* --- 手机 */ +@media screen and (min-width: 600px){ + .row .col-s-auto{ + flex: 1 1 auto; + max-width: 100%; + } + + .row .col-s-1{ + flex: 0 0 8.3333%; + max-width: 8.3333%; + } + + .row .col-s-2{ + flex: 0 0 16.6666%; + max-width: 16.6666%; + } + + .row .col-s-3{ + flex: 0 0 25%; + max-width: 25%; + } + + .row .col-s-4{ + flex: 0 0 33.3333%; + max-width: 33.3333%; + } + + .row .col-s-5{ + flex: 0 0 41.6666%; + max-width: 41.6666%; + } + + .row .col-s-6{ + flex: 0 0 50%; + max-width: 50%; + } + + .row .col-s-7{ + flex: 0 0 58.3333%; + max-width: 58.3333%; + } + + .row .col-s-8{ + flex: 0 0 66.6666%; + max-width: 66.6666%; + } + + .row .col-s-9{ + flex: 0 0 75%; + max-width: 75%; + } + + .row .col-s-10{ + flex: 0 0 83.3333%; + max-width: 83.3333%; + } + + .row .col-s-11{ + flex: 0 0 91.6666%; + max-width: 91.6666%; + } + + .row .col-s-12{ + flex: 0 0 100%; + max-width: 100%; + } +} + +/* --- 平板 */ +@media screen and (min-width: 900px){ + .row .col-m-auto{ + flex: 1 1 auto; + max-width: 100%; + } + + .row .col-m-1{ + flex: 0 0 8.3333%; + max-width: 8.3333%; + } + + .row .col-m-2{ + flex: 0 0 16.6666%; + max-width: 16.6666%; + } + + .row .col-m-3{ + flex: 0 0 25%; + max-width: 25%; + } + + .row .col-m-4{ + flex: 0 0 33.3333%; + max-width: 33.3333%; + } + + .row .col-m-5{ + flex: 0 0 41.6666%; + max-width: 41.6666%; + } + + .row .col-m-6{ + flex: 0 0 50%; + max-width: 50%; + } + + .row .col-m-7{ + flex: 0 0 58.3333%; + max-width: 58.3333%; + } + + .row .col-m-8{ + flex: 0 0 66.6666%; + max-width: 66.6666%; + } + + .row .col-m-9{ + flex: 0 0 75%; + max-width: 75%; + } + + .row .col-m-10{ + flex: 0 0 83.3333%; + max-width: 83.3333%; + } + + .row .col-m-11{ + flex: 0 0 91.6666%; + max-width: 91.6666%; + } + + .row .col-m-12{ + flex: 0 0 100%; + max-width: 100%; + } +} + +/* --- 电脑 */ +@media screen and (min-width: 1024px){ + .row .col-l-auto{ + flex: 1 1 auto; + max-width: 100%; + } + + .row .col-l-1{ + flex: 0 0 8.3333%; + max-width: 8.3333%; + } + + .row .col-l-2{ + flex: 0 0 16.6666%; + max-width: 16.6666%; + } + + .row .col-l-3{ + flex: 0 0 25%; + max-width: 25%; + } + + .row .col-l-4{ + flex: 0 0 33.3333%; + max-width: 33.3333%; + } + + .row .col-l-5{ + flex: 0 0 41.6666%; + max-width: 41.6666%; + } + + .row .col-l-6{ + flex: 0 0 50%; + max-width: 50%; + } + + .row .col-l-7{ + flex: 0 0 58.3333%; + max-width: 58.3333%; + } + + .row .col-l-8{ + flex: 0 0 66.6666%; + max-width: 66.6666%; + } + + .row .col-l-9{ + flex: 0 0 75%; + max-width: 75%; + } + + .row .col-l-10{ + flex: 0 0 83.3333%; + max-width: 83.3333%; + } + + .row .col-l-11{ + flex: 0 0 91.6666%; + max-width: 91.6666%; + } + + .row .col-l-12{ + flex: 0 0 100%; + max-width: 100%; + } +} + +/* -- 网格对齐方式 */ +.row > .left, .row > .right, .row > .top, .row > .bottom, .row > .center{ + display: flex; + flex-direction: column; +} + +.row > .center{ + align-items: center; + justify-content: center; +} + +.row > .center-fixed{ + text-align: center; +} + +.row > .left{ + -webkit-box-align: start; + align-items: flex-start; +} + +.row > .right{ + -webkit-box-align: end; + align-items: flex-end; +} + +.row > .top{ + justify-content: flex-start; +} + +.row > .bottom{ + justify-content: flex-end; +} + +@media screen and (max-width: 900px){ + .row > .to-center{ + align-items: center !important; + } +} + +/* - 隐藏栅格功能 */ +@media screen and (max-width: 600px){ + .row > .hide-s{ display: none; } +} + +@media screen and (max-width: 900px){ + .row > .hide-m{ display: none; } +} + +@media screen and (max-width: 1024px){ + .row > .hide-l{ display: none; } +} + +/* 4 - 动画 +-------------------------------- */ + +/* - 淡入淡出 */ +@keyframes fade-in{ from{ opacity: 0 } to{ opacity: 1 } } +@-webkit-keyframes fade-in{ from{ opacity: 0 } to{ opacity: 1 } } + +@keyframes fade-off{ from{ opacity: 1 } to{ opacity: 0 } } +@-webkit-keyframes fade-off{ from{ opacity: 1 } to{ opacity: 0 } } + +@keyframes fade-in-top{ from{ opacity: 0; transform: translateY(20px) } to{ opacity: 1; transform: translateY(0) } } +@-webkit-keyframes fade-in-top{ from{ opacity: 0; transform: translateY(20px) } to{ opacity: 1; transform: translateY(0) } } + +@keyframes fade-in-bottom{ from{ opacity: 0; transform: translateY(-20px) } to{ opacity: 1; transform: translateY(0) } } +@-webkit-keyframes fade-in-bottom{ from{ opacity: 0; transform: translateY(-20px) } to{ opacity: 1; transform: translateY(0) } } + +@keyframes fade-in-left{ from{ opacity: 0; transform: translateX(20px) } to{ opacity: 1; transform: translateX(0) } } +@-webkit-keyframes fade-in-left{ from{ opacity: 0; transform: translateX(20px) } to{ opacity: 1; transform: translateX(0) } } + +@keyframes fade-in-right{ from{ opacity: 0; transform: translateX(-20px) } to{ opacity: 1; transform: translateX(0) } } +@-webkit-keyframes fade-in-right{ from{ opacity: 0; transform: translateX(-20px) } to{ opacity: 1; transform: translateX(0) } } + +/* - 淡入缩放 */ +@keyframes fade-small-large{ from{ opacity: 0; transform: scale(.5, .5) } to{ opacity: 1; transform: scale(1, 1) } } +@-webkit-keyframes fade-small-large{ from{ opacity: 0; transform: scale(.5, .5) } to{ opacity: 1; transform: scale(1, 1) } } + +@keyframes fade-large-small{ from{ opacity: 1; transform: scale(1, 1) } to{ opacity: 0; transform: scale(.5, .5) } } +@-webkit-keyframes fade-large-small{ from{ opacity: 1; transform: scale(1, 1) } to{ opacity: 0; transform: scale(.5, .5) } } + +@keyframes fade-larger-small{ from{ opacity: 0; transform: scale(1.5, 1.5) } to{ opacity: 1; transform: scale(1, 1) } } +@-webkit-keyframes fade-larger-small{ from{ opacity: 0; transform: scale(1.5, 1.5) } to{ opacity: 1; transform: scale(1, 1) } } + +@keyframes fade-small-larger{ from{ opacity: 1; transform: scale(1, 1) } to{ opacity: 0; transform: scale(1.5, 1.5) } } +@-webkit-keyframes fade-small-larger{ from{ opacity: 1; transform: scale(1, 1) } to{ opacity: 0; transform: scale(1.5, 1.5) } } + +@keyframes scale-small-large{ from{ transform: scale(0, 0) } to{ transform: scale(1, 1) } } +@-webkit-keyframes scale-small-large{ from{ transform: scale(0, 0) } to{ transform: scale(1, 1) } } + +@keyframes scale-large-small{ from{ transform: scale(1, 1) } to{ transform: scale(0, 0) } } +@-webkit-keyframes scale-large-small{ from{ transform: scale(1, 1) } to{ transform: scale(0, 0) } } + +/* - 平移 */ +@keyframes up-and-down{ from{ transform: translateY(-20px) } to{ transform: translateY(20px) } } +@-webkit-keyframes up-and-down{ from{ transform: translateY(-20px) } to{ transform: translateY(20px) } } + +@keyframes left-and-right{ from{ transform: translateX(-20px) } to{ transform: translateX(20px) } } +@-webkit-keyframes left-and-right{ from{ transform: translateX(-20px) } to{ transform: translateX(20px) } } + +/* - 旋转 */ +@keyframes rotate{ from{ transform: rotate(0deg) } to{ transform: rotate(360deg) } } +@-webkit-keyframes rotate{ from{ transform: rotate(0deg) } to{ transform: rotate(360deg) } } + +/* - 弹跳 */ +@keyframes jump{ + 0% { + transform: translateY(0) scale(1.15,.8) + } + + 20% { + transform: translateY(-35px) scaleY(1.1) + } + + 50% { + transform: translateY(-50px) scale(1) + } + + 80% { + transform: translateY(-35px) scale(1) + } + + to { + transform: translateY(0) scale(1.15,.8) + } +} + +/* 5 - 组件 +-------------------------------- */ + +/* - 漂浮提示 */ +[ks-tag]{ position: relative } +[ks-tag]:before, [ks-tag]:after{ position: absolute } + +/* -- 小箭头 */ +[ks-tag]:before{ + width: 0; + height: 0; + opacity: 0; + content: ''; + position: absolute; + transition: opacity .3s; + border: .5rem solid transparent; +} + +[ks-tag~=top]:before{ + bottom: 100%; + border-top-color: rgba(0, 0, 0, .7); +} +[ks-tag~=bottom]:before{ + top: 100%; + border-bottom-color: rgba(0, 0, 0, .7); +} + +[ks-tag~=top]:before, [ks-tag~=bottom]:before{ + left: 50%; + transform: translateX(-50%); +} + +[ks-tag=left]:before{ + right: 100%; + border-left-color: rgba(0, 0, 0, .7); +} +[ks-tag=right]:before{ + left: 100%; + border-right-color: rgba(0, 0, 0, .7); +} + +[ks-tag=left]:before, [ks-tag=right]:before{ + top: 50%; + transform: translateY(-50%); +} + +/* -- 文字 */ +[ks-tag~=top]:after{ + bottom: 100%; + margin-bottom: 1rem; +} +[ks-tag~=bottom]:after{ + top: 100%; + margin-top: 1rem; +} + +[ks-tag=top]:after, [ks-tag=bottom]:after{ + left: 50%; + transform: translateX(-50%); +} + +[ks-tag=left]:after{ + right: 100%; + margin-right: 1rem; +} +[ks-tag=right]:after{ + left: 100%; + margin-left: 1rem; +} + +[ks-tag=left]:after, [ks-tag=right]:after{ + top: 50%; + transform: translateY(-50%); +} + +/* -- 组合对齐方式 */ +[ks-tag~=left][ks-tag~=top]:after, [ks-tag~=left][ks-tag~=bottom]:after{ + right: 0; + min-width: 4em; +} +[ks-tag~=right][ks-tag~=top]:after, [ks-tag~=right][ks-tag~=bottom]:after{ + left: 0; + min-width: 4em; +} + +[ks-text]:hover:before, [ks-text]:hover:after{ opacity: 1 } + +[ks-text]:after{ + opacity: 0; + z-index: 1; + color: #fff; + font-size: .85em; + border-radius: .5em; + white-space: nowrap; + pointer-events: none; + padding: .25rem .5rem; + content: attr(ks-text); + transition: opacity .3s; + background: rgba(0, 0, 0, .7); +} + +/* - 弹窗 */ +notice{ + top: 0; + left: 0; + right: 0; + z-index: 10; + padding: 1em; + position: fixed; + user-select: none; + pointer-events: none; +} + +.ks-notice{ + color: #fff; + display: table; + background: #333; + border-radius: 3em; + pointer-events: auto; + margin: 0 auto 1em auto; + box-shadow: 0 5px 5px -2px rgba(0, 0, 0, .2); + animation: fade-small-large .3s both; + -webkit-animation: fade-small-large .3s both; +} + +.ks-notice.remove{ + animation: fade-in-top .3s both reverse; + -webkit-animation: fade-in-top .3s both reverse; +} + +/* -- 弹窗颜色 */ +.ks-notice.red{ + background: #ea644a; + background: var(--red); +} + +.ks-notice.yellow{ + background: #ffb03a; + background: var(--yellow); +} + +.ks-notice.blue{ + background: #3498db; + background: var(--blue); +} + +.ks-notice.green{ + background: #27a17e; + background: var(--green); +} + +.ks-notice > span{ + padding: .5em 1em; + display: table-cell; + vertical-align: middle; +} + +.ks-notice .close{ + cursor: pointer; + border-radius: 0 1em 1em 0; + transition: background .3s; +} + +.ks-notice .close:hover{ + background: rgba(0, 0, 0, .1); +} + +.ks-notice .close:after{ + content: '×'; + font: inherit; +} + +/* - 图片放大 */ +[ks-image=active]{ + cursor: pointer; + cursor: zoom-in; +} + +.ks-image{ + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 66; + position: fixed; + user-select: none; + animation: fade-in .3s both; + -webkit-animation: fade-in .3s both; +} +.ks-image.loading{ cursor: wait } +.ks-image.remove:before{ + animation: fade-off .3s both; + -webkit-animation: fade-off .3s both; +} + +.ks-image:before{ + top: 0; + left: 0; + right: 0; + bottom: 0; + content: ''; + position: absolute; + background: rgba(0, 0, 0, .6); +} + +.ks-image .ks-prev, .ks-image .ks-next{ + top: 0; + bottom: 0; + width: 10%; + height: 5em; + margin: auto; + max-width: 5em; + cursor: pointer; + position: absolute; + transition: opacity .3s, transform .3s; +} +.ks-image .ks-prev:hover{ transform: translateX(-.5em) } +.ks-image .ks-next:hover{ transform: translateX(.5em) } + +.ks-image .ks-prev{ + left: 0; + background: center/60% no-repeat url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZmlsbD0icmdiYSgyNTUsMjU1LDI1NSwwLjkpIj48cGF0aCBkPSJNMzI0LjIxMTUxNyA1MTEuODA1NjMxIDc4Ny44ODk1OTQgNzMuMDgyNTgzYzE2LjE5NDIyLTE2LjYzMDM2NSAxNi4xOTQyMi00My45NzQ3MDQgMC02MC42MDUwNjgtMTYuMTk0MjItMTYuNjMwMzY1LTQyLjQ5NTYwNy0xNi42MzAzNjUtNTguNjEzOTc2IDBMMjM1Ljc1MDExMyA0NzkuMzYwMzAyYy04LjY0NzAzMSA4Ljk2OTM5OC0xMi4zNDQ3NzUgMjAuOTM0OTE3LTExLjcxOTAwMyAzMi40NDUzMjktMC42NDQ3MzUgMTEuOTA4NjMgMy4wNzE5NzIgMjMuODc0MTQ5IDExLjcxOTAwMyAzMi44MjQ1ODVsNDkzLjUwNjU0MiA0NjYuODgyNzg4YzE2LjExODM2OSAxNi42NDkzMjcgNDIuNDM4NzE4IDE2LjY0OTMyNyA1OC42MTM5NzYgMCAxNi4xOTQyMi0xNy4wODU0NzEgMTYuMTk0MjItNDMuOTc0NzA0IDAtNjAuNjA1MDY4TDMyNC4yMTE1MTcgNTExLjgwNTYzMSI+PC9wYXRoPjwvc3ZnPg==); +} +.ks-image .ks-next{ + right: 0; + background: center/60% no-repeat url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjAwIDIwMCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9InJnYmEoMjU1LDI1NSwyNTUsMC45KSI+PHBhdGggZD0iTTEzNi43LDEwMGwtOTAuNiw4NS44Yy0zLjIsMy4yLTMuMiw4LjUsMCwxMS44YzMuMiwzLjMsOC4zLDMuMywxMS40LDBsOTYuNC05MS4yYzEuNy0xLjcsMi40LTQuMSwyLjMtNi40YzAuMS0yLjItMC42LTQuNi0yLjMtNi4zTDU3LjYsMi40Yy0zLjEtMy4yLTguMy0zLjItMTEuNCwwcy0zLjIsOC42LDAsMTEuOEwxMzYuNywxMDAiLz48L3N2Zz4NCg==); +} + +.ks-image .ended{ + opacity: .5; + cursor: no-drop; +} + +.ks-image .ks-ball{ + top: 1em; + right: 1em; + width: 2em; + height: 2em; + opacity: 0; + border-radius: 66%; + position: absolute; + pointer-events: none; + transition: opacity .3s; + border: .5em #fff solid; + border-left-color: #3498db; + border-left-color: var(--primary); + animation: rotate .5s linear infinite paused; + -webkit-animation: rotate .5s linear infinite paused; +} +.ks-image.loading .ks-ball{ + opacity: 1; + animation-play-state: running; +} + +.ks-image img{ + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto; + max-width: 80%; + max-height: 90%; + cursor: zoom-out; + position: absolute; + transition: transform .3s; + animation: fade-small-large .3s backwards; + -webkit-animation: fade-small-large .3s backwards; +} +.ks-image.remove img, .ks-image.remove .ks-prev, .ks-image.remove .ks-next{ + animation: fade-large-small .3s both; + -webkit-animation: fade-large-small .3s both; +} + +.ks-image img[src$=".jpg"]{ box-shadow: 0 5px 15px rgba(0, 0, 0, .5) } + +/* ---- + +Copyright (C) 2021 Dreamer-Paul. + +---- */ diff --git a/static/kico.js b/static/kico.js new file mode 100755 index 0000000..b8ae239 --- /dev/null +++ b/static/kico.js @@ -0,0 +1,346 @@ +/* ---- + +# Kico Style 2.1 +# By: Dreamer-Paul +# Last Update: 2018.12.25 + +一个简洁、有趣的开源响应式框架,仅包含基础样式,需按照一定规则进行二次开发。 + +欢迎你加入缤奇,和我们一起改变世界。 +本代码为奇趣保罗原创,并遵守 MIT 开源协议。欢迎访问我的博客:https://paugram.com + +---- */ + +function Kico_Style () { + var kico = {}; + var that = this; + + // 批量执行 + this.forEach = function (data, fn) { + for(var i = 0; i < data.length; i++){ + fn(data[i], i, data); + } + }; + + // 对象创建 + this.create = function (tag, prop) { + var obj = document.createElement(tag); + + if(prop){ + if(prop.id) obj.id = prop.id; + if(prop.href) obj.href = prop.href; + if(prop.class) obj.className = prop.class; + if(prop.text) obj.innerText = prop.text; + if(prop.html) obj.innerHTML = prop.html; + + if(prop.child){ + if(prop.child.constructor === Array){ + that.forEach(prop.child, function (i) { + obj.appendChild(i); + }); + } + else{ + obj.appendChild(prop.child); + } + } + } + + return obj; + }; + + // 对象选择 + this.select = function (obj) { + if(typeof obj === 'object'){ + return obj; + } + else if(typeof obj === 'string'){ + return document.querySelector(obj); + } + }; + + this.selectAll = function (obj) { + if(typeof obj === 'object'){ + return obj; + } + else if(typeof obj === 'string'){ + return document.querySelectorAll(obj); + } + }; + + // 弹窗 + kico.notice_list = this.create("div", {class: "ks-notice-list"}); + + this.notice = function (content, attr) { + var item = that.create("div", {class: "ks-notice"}); + item.innerHTML += "" + content + ""; + + kico.notice_list.appendChild(item); + + if(!document.querySelector("body > .ks-notice-list")) document.body.appendChild(kico.notice_list); + + if(attr && attr.time){ + setTimeout(notice_remove, attr.time); + } + else{ + var close = this.create("span", {class: "close"}); + + close.addEventListener("click", function () { + notice_remove(); + }); + + item.classList.add("dismiss"); + item.appendChild(close); + } + + if(attr && attr.color){item.classList.add(attr.color);} + + function notice_remove() { + item.classList.add("remove"); + + setTimeout(function () { + try{ + kico.notice_list.removeChild(item); + item = null; + } + catch(err) {} + + if(document.querySelector("body > .ks-notice-list") && kico.notice_list.childNodes.length === 0){ + document.body.removeChild(kico.notice_list); + } + }, 300); + } + }; + + // 遮罩 + kico.overlay = this.create("div", {class: "ks-overlay"}); + + this.overlay = function (attr) { + document.body.appendChild(kico.overlay); + + if(attr && attr.time){ + setTimeout(overlay_remove, attr.time); + } + else{ + kico.overlay.onclick = function (){ overlay_remove() }; + } + + if(attr && attr.code){ + kico.overlay.onclick = function (){ overlay_remove(); attr.code() } + } + + function overlay_remove() { + kico.overlay.onclick = null; + kico.overlay.classList.add("remove"); + + setTimeout(function () { + if(document.querySelector("body > .ks-overlay")){ + kico.overlay.classList.remove("remove"); + document.body.removeChild(kico.overlay); + } + }, 300); + } + }; + + // 图片缩放 + kico.image_box = []; + kico.image_box.img = this.create("img"); + kico.image_box.prev = this.create("div", {class: "ks-prev"}); + kico.image_box.next = this.create("div", {class: "ks-next"}); + kico.image_box.ball = this.create("div", {class: "ks-ball"}); + + kico.image_box.wrap = this.create("div", {class: "ks-image", child: [ + kico.image_box.img, kico.image_box.prev, kico.image_box.next, kico.image_box.ball + ]}); + + this.image = function (selector) { + var current = 0; + var get_images = this.selectAll(selector); + + // 设置图片 + function set_image(img) { + if(img.getAttribute("ks-original") !== null){ + kico.image_box.img.src = img.getAttribute("ks-original"); + } + else if(img.src){ + kico.image_box.img.src = img.src; + } + + kico.image_box.ball.classList.add("loading"); + } + + // 设置按钮 + function set_buttons(c) { + function l(){ + if(current - 1 >= 0) current--; + + set_image(get_images[current]); + } + + function r(){ + if(current + 1 < get_images.length) current++; + + set_image(get_images[current]); + } + + kico.image_box.prev.onclick = l; + kico.image_box.next.onclick = r; + } + + // 循环内单条设定 + function set_item(obj, num) { + var scale = 1; + var locationX, locationY; + + obj.setAttribute("ks-image", "active"); + + /*kico.image_single.onmousewheel = function () { + console.log(event); + if(event.wheelDelta > 0 || event.deltaY < 0){ + if(scale > 1){ + scale = scale - 0.1; + } + } + else{ + if(scale >= 1){ + scale = scale + 0.1; + } + } + + /*if(event.offsetX / window.innerWidth > 0.5){ + //locationX = "right"; + locationX = parseInt((event.offsetX / window.innerWidth) * 100) + "%"; + console.log("kuan" + window.innerWidth); + console.log(event.offsetX); + //console.log(window.innerWidth / ); + } + else{ + locationX = parseInt((event.offsetX / window.innerWidth) * 100) + "%"; + //locationX = "left"; + }*/ + + /*locationX = (event.offsetX / window.innerWidth) * 100 + "%"; + locationY = (event.offsetY / window.innerHeight) * 100 + "%"; + + //event.offsetY / window.innerHeight > 0.5 ? locationY = "bottom" : locationY = "top"; + + kico.image_single.style.transform = "scale(" + scale + ")"; + kico.image_single.style.transformOrigin = locationX + " " + locationY; + };*/ + + function single_click(){ + /*scale = 1; + locationX = "50%"; + locationY = "50%";*/ + + current = num; + console.log("现在选中了" + current); + + //kico.image_single.style.transform = "scale(" + scale + ")"; + //kico.image_single.style.transformOrigin = locationX + " " + locationY; + + set_image(obj); + set_buttons(num); + + if(!document.body.contains(kico.image_box.wrap)) { + document.body.appendChild(kico.image_box.wrap); + } + } + + obj.onclick = single_click; + } + + this.forEach(get_images, function (i, j) { + if(i.src || i.getAttribute("ks-original")){ + set_item(i, j); + } + }); + + kico.image_box.img.onclick = function () { + kico.image_box.wrap.classList.add("remove"); + setTimeout(function () { + try{ + document.body.removeChild(kico.image_box.wrap); + kico.image_box.wrap.classList.remove("remove"); + } + catch (err){} + }, 300); + }; + + kico.image_box.img.onload = function () { + kico.image_box.ball.classList.remove("loading"); + } + }; + + // AJAX 组件 + this.ajax = function (prop) { + if(!prop.url){ + prop.url = document.location.origin + document.location.pathname; + } + + function test_crossDomain() { + var a = document.createElement("a"); + a.href = window.location.hostname; + return a.hostname === prop.url ? true : false; + } + + if(prop.method === "POST"){ + var data = new FormData(); + + for(var pk in prop.data){ + data.append(pk, prop.data[pk]); + } + } + else if(prop.method === "GET"){ + var url = prop.url + "?"; + + for(var k in prop.data){ + url += k + "=" + prop.data[k] + "&"; + } + + prop.url = url.substr(0, url.length - 1); + } + + var request = new XMLHttpRequest(); + request.open(prop.method, prop.url); + if(prop.crossDomain){ request.setRequestHeader("X-Requested-With", "XMLHttpRequest"); } + + if(prop.header){ + for(var i in prop.header){ + request.setRequestHeader(prop.header[i][0], prop.header[i][1]); + } + } + + request.send(data); + + request.onreadystatechange = function () { + if(request.readyState === 4){ + if(request.status === 200 || request.status === 304){ + prop.success ? prop.success(request) : console.log(prop.method + " 请求发送成功"); + } + else{ + prop.failed ? prop.failed(request) : console.log(prop.method + " 请求发送失败"); + } + } + }; + }; + + // 返回页头 + this.scrollTop = function () { + function to_top(){ + if(window.scrollY !== 0){ + window.scrollTo(0, window.scrollY * 0.9); + requestAnimationFrame(to_top); + } + else{ + cancelAnimationFrame(this); + } + } + + window.requestAnimationFrame(to_top); + }; + + // Show Version + console.log("%c Kico Style %c https://paugram.com ","color: #fff; margin: 1em 0; padding: 5px 0; background: #3498db;","margin: 1em 0; padding: 5px 0; background: #efefef;"); +} + +var ks = new Kico_Style(); \ No newline at end of file diff --git a/static/kico.svg b/static/kico.svg new file mode 100755 index 0000000..5c9a24b --- /dev/null +++ b/static/kico.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + +