mirror of
https://github.com/javaLuo/vue-flip-down.git
synced 2025-04-06 03:58:09 +08:00
first commit
This commit is contained in:
parent
e7327debb7
commit
11bd22a50f
1
dist/main.js
vendored
Normal file
1
dist/main.js
vendored
Normal file
File diff suppressed because one or more lines are too long
16
example/index.html
Normal file
16
example/index.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-cn">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"
|
||||
/>
|
||||
<title>Example</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="bundle.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
8
example/main.js
Normal file
8
example/main.js
Normal file
@ -0,0 +1,8 @@
|
||||
import Vue from 'vue';
|
||||
import App from './src/app.vue';
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
new Vue({
|
||||
render: h => h(App),
|
||||
}).$mount('#root');
|
29
example/src/app.vue
Normal file
29
example/src/app.vue
Normal file
@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div>
|
||||
<CountDown :endDate="endDate"
|
||||
:type="type" />
|
||||
|
||||
<hr />
|
||||
<input v-model="type">
|
||||
<input v-model="endDate">
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import CountDown from '../../dist/main.js';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
type: 4,
|
||||
endDate: new Date().getTime() + 100861100,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
CountDown,
|
||||
},
|
||||
methods: {
|
||||
onTypeChange(t) {
|
||||
this.type = t;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
61
package.json
Normal file
61
package.json
Normal file
@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "vue-flip-down",
|
||||
"version": "1.0.0",
|
||||
"description": "vue 翻页效果的倒计时组件",
|
||||
"main": "dist/main.js",
|
||||
"scripts": {
|
||||
"dev": "webpack-dev-server --config webpack.dev.config.js",
|
||||
"build": "webpack --config webpack.production.config.js --progress --profile --colors",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/javaLuo/vue-flip-down.git"
|
||||
},
|
||||
"keywords": [
|
||||
"vue"
|
||||
],
|
||||
"author": "L",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/javaLuo/vue-flip-down/issues"
|
||||
},
|
||||
"homepage": "https://github.com/javaLuo/vue-flip-down#readme",
|
||||
"dependencies": {
|
||||
"vue": "^2.5.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^9.1.1",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-loader": "^7.1.5",
|
||||
"babel-plugin-import": "^1.8.0",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.5",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"babel-preset-vue-app": "^2.0.0",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"clean-webpack-plugin": "^0.1.19",
|
||||
"css-loader": "^1.0.0",
|
||||
"less": "^3.8.1",
|
||||
"less-loader": "^4.1.0",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"style-loader": "^0.22.1",
|
||||
"uglifyjs-webpack-plugin": "^1.3.0",
|
||||
"url-loader": "^1.1.1",
|
||||
"vue-loader": "^15.3.0",
|
||||
"vue-template-compiler": "^2.5.17",
|
||||
"webpack": "^4.16.5",
|
||||
"webpack-cli": "^3.1.0",
|
||||
"webpack-dev-server": "^3.1.5"
|
||||
},
|
||||
"browserslist": [
|
||||
"iOS >= 8",
|
||||
"Android > 4.1",
|
||||
"last 1 versions",
|
||||
"> 1%",
|
||||
"not dead"
|
||||
]
|
||||
}
|
4
postcss.config.js
Normal file
4
postcss.config.js
Normal file
@ -0,0 +1,4 @@
|
||||
/** postcss-loader 解析器所需的配置文件 **/
|
||||
module.exports = {
|
||||
plugins: [require("autoprefixer")()]
|
||||
};
|
307
src/app.vue
Normal file
307
src/app.vue
Normal file
@ -0,0 +1,307 @@
|
||||
<!-- 翻页效果 倒计时组件 -->
|
||||
<template>
|
||||
<div class="vue-countdown-component">
|
||||
<!-- 天 -->
|
||||
<div class="time-box"
|
||||
v-if="type>=4">
|
||||
{{day}}
|
||||
<div :class="['b0',{'anime': isDayAnime}]">
|
||||
<div>{{day}}</div>
|
||||
</div>
|
||||
<div :class="['a0',{'anime': isDayAnime}]"
|
||||
@animationend="onDayAnimateEnd">
|
||||
<div>{{dayDelay}}</div>
|
||||
</div>
|
||||
<div class="a1">
|
||||
<div>{{dayDelay}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 时 -->
|
||||
<div class="time-box"
|
||||
v-if="type>=3">
|
||||
{{hour}}
|
||||
<div :class="['b0',{'anime': isHourAnime}]">
|
||||
<div>{{hour}}</div>
|
||||
</div>
|
||||
<div :class="['a0',{'anime': isHourAnime}]"
|
||||
@animationend="onHourAnimateEnd">
|
||||
<div>{{hourDelay}}</div>
|
||||
</div>
|
||||
<div class="a1">
|
||||
<div>{{hourDelay}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分 -->
|
||||
<div class="time-box"
|
||||
v-if="type>=2">
|
||||
{{min}}
|
||||
<div :class="['b0',{'anime': isMinAnime}]">
|
||||
<div>{{min}}</div>
|
||||
</div>
|
||||
<div :class="['a0',{'anime': isMinAnime}]"
|
||||
@animationend="onMinAnimateEnd">
|
||||
<div>{{minDelay}}</div>
|
||||
</div>
|
||||
<div class="a1">
|
||||
<div>{{minDelay}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 秒 -->
|
||||
<div class="time-box">
|
||||
{{second}}
|
||||
<div :class="['b0',{'anime': isSecondAnime}]">
|
||||
<div>{{second}}</div>
|
||||
</div>
|
||||
<div :class="['a0',{'anime': isSecondAnime}]"
|
||||
@animationend="onSecondAnimateEnd">
|
||||
<div>{{secondDelay}}</div>
|
||||
</div>
|
||||
<div class="a1">
|
||||
<div>{{secondDelay}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
day: '', // 剩余日
|
||||
dayDelay: '',
|
||||
hour: '', // 剩余小时
|
||||
hourDelay: '',
|
||||
min: '', // 剩余分钟
|
||||
minDelay: '',
|
||||
second: '', // 剩余秒
|
||||
secondDelay: '',
|
||||
timer: null, // 计时器
|
||||
isDayAnime: false, // 日 执行动画
|
||||
isHourAnime: false, // 时 执行动画
|
||||
isMinAnime: false, // 分 执行动画
|
||||
isSecondAnime: false, // 秒 执行动画
|
||||
};
|
||||
},
|
||||
props: {
|
||||
endDate: { type: [Date, Number, String], default: 0 }, // 截止时间
|
||||
type: { type: [Number, String], default: 4 }, // 时间精度 4/3/2/1
|
||||
},
|
||||
computed: {
|
||||
endTime() {
|
||||
if (this.endDate instanceof Date) {
|
||||
return this.endDate.getTime();
|
||||
}
|
||||
return Number(this.endDate) > 0 ? Number(this.endDate) : 0;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
day(newV) {
|
||||
this.isDayAnime = true;
|
||||
setTimeout(() => {
|
||||
this.dayDelay = newV;
|
||||
}, 350);
|
||||
},
|
||||
hour(newV) {
|
||||
this.isHourAnime = true;
|
||||
setTimeout(() => {
|
||||
this.hourDelay = newV;
|
||||
}, 350);
|
||||
},
|
||||
min(newV) {
|
||||
this.isMinAnime = true;
|
||||
setTimeout(() => {
|
||||
this.minDelay = newV;
|
||||
}, 350);
|
||||
},
|
||||
second(newV) {
|
||||
this.isSecondAnime = true;
|
||||
setTimeout(() => {
|
||||
this.secondDelay = newV;
|
||||
}, 350);
|
||||
},
|
||||
endTime(newV) {
|
||||
if (newV > 0) {
|
||||
this.start();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.start();
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearTimeout(this.timer);
|
||||
},
|
||||
methods: {
|
||||
// 开始倒计时
|
||||
start() {
|
||||
clearTimeout(this.timer);
|
||||
console.log('每秒1次啊');
|
||||
this.timer = setTimeout(() => {
|
||||
let t = this.endTime - new Date().getTime(); // 剩余的毫秒数
|
||||
t = t < 0 ? 0 : t;
|
||||
let day = 0; // 剩余的天
|
||||
let hour = 0; // 剩余的小时 已排除天
|
||||
let min = 0; // 剩余的分钟 已排除天和小时
|
||||
let second = 0; // 剩余的秒
|
||||
let type = Number(this.type);
|
||||
if (type >= 4) {
|
||||
day = Math.floor(t / 86400000); // 剩余的天
|
||||
hour = Math.floor(t / 3600000 - day * 24); // 剩余的小时 已排除天
|
||||
min = Math.floor(t / 60000 - day * 1440 - hour * 60); // 剩余的分钟 已排除天和小时
|
||||
second = Math.floor(t / 1000 - day * 86400 - hour * 3600 - min * 60); // 剩余的秒
|
||||
} else if (type >= 3) {
|
||||
hour = Math.floor(t / 3600000); // 剩余的小时 已排除天
|
||||
min = Math.floor(t / 60000 - hour * 60); // 剩余的分钟 已排除天和小时
|
||||
second = Math.floor(t / 1000 - hour * 3600 - min * 60); // 剩余的秒
|
||||
} else if (type >= 2) {
|
||||
min = Math.floor(t / 60000); // 剩余的分钟 已排除天和小时
|
||||
second = Math.floor(t / 1000 - min * 60); // 剩余的秒
|
||||
} else {
|
||||
second = Math.floor(t / 1000); // 剩余的秒
|
||||
}
|
||||
|
||||
this.day = String(day).padStart(2, '0');
|
||||
this.hour = String(hour).padStart(2, '0');
|
||||
this.min = String(min).padStart(2, '0');
|
||||
this.second = String(second).padStart(2, '0');
|
||||
|
||||
if (t > 0) this.start();
|
||||
}, 1000);
|
||||
},
|
||||
// 日 动画结束
|
||||
onDayAnimateEnd() {
|
||||
this.isDayAnime = false;
|
||||
},
|
||||
onHourAnimateEnd() {
|
||||
this.isHourAnime = false;
|
||||
},
|
||||
onMinAnimateEnd() {
|
||||
this.isMinAnime = false;
|
||||
},
|
||||
onSecondAnimateEnd() {
|
||||
this.isSecondAnime = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.vue-countdown-component {
|
||||
display: flex;
|
||||
@keyframes animate-filp {
|
||||
0% {
|
||||
transform: rotateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: rotateX(-180deg);
|
||||
}
|
||||
}
|
||||
@keyframes animate-filp2 {
|
||||
0% {
|
||||
transform: rotateX(180deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotateX(0);
|
||||
}
|
||||
}
|
||||
.time-box {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
height: 30px;
|
||||
min-width: 28px;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
background-color: #6c96e8;
|
||||
color: #ffffff;
|
||||
perspective: 50px;
|
||||
border-radius: 3px;
|
||||
padding: 0 2px;
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background: #a7c7ff;
|
||||
width: 2px;
|
||||
height: 6px;
|
||||
top: 50%;
|
||||
left: -1px;
|
||||
margin-top: -3px;
|
||||
}
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background: #a7c7ff;
|
||||
width: 2px;
|
||||
height: 6px;
|
||||
top: 50%;
|
||||
right: -1px;
|
||||
margin-top: -3px;
|
||||
}
|
||||
& + .time-box {
|
||||
margin-left: 8px;
|
||||
}
|
||||
& > div {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
overflow: hidden;
|
||||
|
||||
transform-style: preserve-3d;
|
||||
& > div {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
}
|
||||
&.a0 {
|
||||
top: 0;
|
||||
// opacity: 0;
|
||||
border-radius: 3px 3px 0 0;
|
||||
background-color: #6c96e8;
|
||||
transform-origin: 50% bottom;
|
||||
animation-duration: 500ms;
|
||||
// animation-fill-mode: none;
|
||||
transform: rotateX(0);
|
||||
backface-visibility: hidden;
|
||||
z-index: 2;
|
||||
&.anime {
|
||||
animation-name: animate-filp;
|
||||
}
|
||||
& > div {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
&.b0 {
|
||||
top: 15px;
|
||||
border-radius: 0 0 3px 3px;
|
||||
background-color: #73a1f8;
|
||||
transform-origin: 50% top;
|
||||
animation-duration: 500ms;
|
||||
// animation-fill-mode: none;
|
||||
transform: rotateX(180deg);
|
||||
backface-visibility: hidden;
|
||||
z-index: 2;
|
||||
& > div {
|
||||
bottom: 0;
|
||||
}
|
||||
&.anime {
|
||||
animation-name: animate-filp2;
|
||||
}
|
||||
}
|
||||
&.a1 {
|
||||
top: 15px;
|
||||
border-radius: 0 0 3px 3px;
|
||||
background-color: #73a1f8;
|
||||
& > div {
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
2
src/index.js
Normal file
2
src/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
import App from './app.vue';
|
||||
export default App;
|
28
webpack.dev.config.js
Normal file
28
webpack.dev.config.js
Normal file
@ -0,0 +1,28 @@
|
||||
var path = require('path');
|
||||
const VueLoaderPlugin = require('vue-loader/lib/plugin');
|
||||
|
||||
module.exports = {
|
||||
mode: 'development',
|
||||
entry: path.join(__dirname, 'example', 'main.js'),
|
||||
output: {
|
||||
filename: 'bundle.js',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /.vue$/,
|
||||
use: ['vue-loader'],
|
||||
include: [path.join(__dirname, 'example')],
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ['babel-loader'],
|
||||
include: [path.join(__dirname, 'example')],
|
||||
},
|
||||
],
|
||||
},
|
||||
devServer: {
|
||||
contentBase: path.join(__dirname, 'example'),
|
||||
},
|
||||
plugins: [new VueLoaderPlugin()],
|
||||
};
|
79
webpack.production.config.js
Normal file
79
webpack.production.config.js
Normal file
@ -0,0 +1,79 @@
|
||||
/** 这是用于开发环境的webpack配置文件 **/
|
||||
|
||||
const path = require('path'); // 获取绝对路径用
|
||||
const webpack = require('webpack'); // webpack核心
|
||||
const CleanWebpackPlugin = require('clean-webpack-plugin'); // 每次打包前清除旧的build文件夹
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
const VueLoaderPlugin = require('vue-loader/lib/plugin');
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
entry: [
|
||||
'./src/index.js', // 项目入口
|
||||
],
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'), // 将打包好的文件放在此路径下,dev模式中,只会在内存中存在,不会真正的打包到此路径
|
||||
filename: '[name].js', //编译后的文件名字
|
||||
library: ['vue-flip-down'],
|
||||
libraryTarget: 'umd',
|
||||
},
|
||||
externals: {
|
||||
vue: 'vue',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.vue$/,
|
||||
use: ['vue-loader'],
|
||||
include: path.resolve(__dirname, 'src'),
|
||||
},
|
||||
{
|
||||
// .js .jsx用babel解析
|
||||
test: /\.js?$/,
|
||||
use: ['babel-loader'],
|
||||
include: path.resolve(__dirname, 'src'),
|
||||
},
|
||||
// {
|
||||
// // .css 解析
|
||||
// test: /\.css$/,
|
||||
// use: ['style-loader', 'css-loader', 'postcss-loader'],
|
||||
// include: path.resolve(__dirname, 'src'),
|
||||
// },
|
||||
{
|
||||
// .less 解析
|
||||
test: /\.less$/,
|
||||
use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader'],
|
||||
include: path.resolve(__dirname, 'src'),
|
||||
},
|
||||
{
|
||||
// 文件解析
|
||||
test: /\.(eot|woff|otf|svg|ttf|woff2|appcache|mp3|mp4|pdf)(\?|$)/,
|
||||
include: path.resolve(__dirname, 'src'),
|
||||
use: ['file-loader?name=assets/[name].[ext]'],
|
||||
},
|
||||
{
|
||||
// 图片解析
|
||||
test: /\.(png|jpg|gif)(\?|$)/,
|
||||
include: path.resolve(__dirname, 'src'),
|
||||
use: ['url-loader?limit=8192&name=assets/[name].[ext]'],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new VueLoaderPlugin(),
|
||||
new CleanWebpackPlugin(['dist']),
|
||||
new UglifyJsPlugin({
|
||||
uglifyOptions: {
|
||||
compress: {
|
||||
drop_console: true, // 是否删除代码中所有的console
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
extensions: ['.js', '.vue', '.less', '.css'], //后缀名自动补全
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, 'src'),
|
||||
},
|
||||
},
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user