first commit

This commit is contained in:
harriet 2020-02-03 21:34:20 +08:00
parent 66fdc587f3
commit 81c2b70d0c
12 changed files with 3935 additions and 0 deletions

21
.babelrc Normal file
View File

@ -0,0 +1,21 @@
{
"presets": [
[
"env",
{
"modules": false,
"targets": {
"browsers": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}
}
],
"stage-2"
],
"plugins": [
"transform-runtime"
]
}

8
.npmignore Normal file
View File

@ -0,0 +1,8 @@
.*
*.md
*.yml
build/
node_modules/
src/
test/
gulpfile.js

41
README.md Normal file
View File

@ -0,0 +1,41 @@
# vue-flip-page
vue翻页组件
方法
change (改变页面)
tap  (点击)
turning (正在翻页)
prev (前一页)
next (后一页)
翻到指定页面:
handleSwitchManual(index) {
if (index === this.currentIndex) return;
this.$refs["turn"].toPage(index);
this.currentIndex = index;
this.goods_id = this.manuals[this.currentIndex].goods_id;
this.show = false;
},
传入参数:
| 参数 | type | example | describe |
| ------ | ---- | -------- | ---------- |
| width | number | 375 | 宽度 |
| height | number | 667 | 高度 |
| data | Array | [ { "picture_image": "https://dev8.yunzmall.com/static/upload/image/8a2b418254cf521ff3e668fa33ac07ee.png", }, { "picture_image": "https://dev8.yunzmall.com/static/upload/image/e7cf7880531ff9cbb91902630c808359.png", }] |传入的数据 |
npm包  npm install vue-calendar-l
在需要用到的页面中(注意 一个页面目前只能引入一次)
import flipPage from "vue-flip-page";
components: { flipPage }
例子:
效果:

2461
dist/flip.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/flip.min.js.map vendored Normal file

File diff suppressed because one or more lines are too long

51
package.json Normal file
View File

@ -0,0 +1,51 @@
{
"name": "vue-flip-page",
"version": "1.0.1",
"description": "vue翻页效果插件",
"main": "dist/flip.min.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack-dev-server --hot --inline",
"build": "webpack --display-error-details --config webpack.config.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/harrietjia/vue-flip-page.git"
},
"keywords": [
"vue",
"flip-page",
"vue-flip-page"
],
"author": "harrietjia",
"Email": "1317499207@qq.com",
"license": "ISC",
"bugs": {
"url": "https://github.com/harrietjia/vue-flip-page/issues"
},
"homepage": "https://github.com/harrietjia/vue-flip-page#readme",
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.26.0",
"babel-preset-es2015": "^6.24.1",
"css-loader": "^0.28.7",
"es6-promise": "^4.1.1",
"node-sass": "^4.11.0",
"sass-loader": "^6.0.7",
"style-loader": "^0.19.0",
"url-loader": "^0.6.2",
"vue": "^2.5.9",
"vue-hot-reload-api": "^2.2.4",
"vue-html-loader": "^1.2.4",
"vue-loader": "^13.5.0",
"vue-router": "^3.0.1",
"vue-style-loader": "^3.0.3",
"vue-template-compiler": "^2.5.9",
"vuex": "^3.0.1",
"webpack": "^3.9.1",
"webpack-dev-server": "^2.9.5"
}
}

227
src/App.vue Normal file
View File

@ -0,0 +1,227 @@
<template>
<div class="turn" ref="turn" :style="{width: width+'px', height: height+'px'}">
<template v-for="(item,index) in data">
<turn-page v-if="index == backPage || index == turnPage" :index="index" :width="width" :item="item" :height="height" :length="data.length"
:active="index == turnPage && turnActive" :styles="index === turnPage ? styles : defaultStyles">
</turn-page>
</template>
<div class="turn-right-border" :style="{width: (((Math.min(data.length, 10) / data.length * (data.length - backPage))) + 'px')}"></div>
</div>
</template>
<script>
import turn_controller from "./turn_controller";
export default turn_controller;
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
.turn {
position: relative;
z-index: 0;
-webkit-box-shadow: 0 0 0.06rem 0 rgba(0, 0, 0, 0.60);
box-shadow: 0 0 0.06rem 0 rgba(0, 0, 0, 0.60);
-webkit-transition: all ease 0.35s;
transition: all ease 0.35s;
}
.manual-item {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #fff;
-webkit-transition: all ease 0.35s;
transition: all ease 0.35s;
}
.manual-page {
position: relative;
width: 100%;
height: 100%;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.manual-page.loading {
background: url(/article-h5/static/img/loading.95eeac7.gif) no-repeat center center !important;
background-size: 60px !important;
}
.page-photo {
position: relative;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
font-size: 0;
line-height: 0;
width: 100%;
height: 100%;
opacity: 1;
-webkit-transition: opacity ease 0.2s;
transition: opacity ease 0.2s;
}
.manual-page.loading .page-photo {
opacity: 0;
}
.page-photo::after {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 100%;
z-index: 2;
pointer-events: none;
background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 0.2)), color-stop(6%, rgba(255, 255, 255, 0.15)), to(rgba(255, 255, 255, 0.15)));
background-image: linear-gradient(90deg, rgba(0, 0, 0, 0.2) 0%, rgba(255, 255, 255, 0.15) 6%, rgba(255, 255, 255, 0.15) 100%);
}
.page-photo img {
display: inline-block;
max-width: 100%;
max-height: 100%;
}
.turn-right-border {
position: absolute;
top: 0;
bottom: 0;
right: 0;
z-index: 99;;
-webkit-transform: translate(100%);;
transform: translate(100%);
width: 10px;
-webkit-perspective: 500px;
perspective: 500px;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
}
.turn-right-border::after {
position: relative;
content: '';
display: block;
width: 100%;
height: 100%;
background: #fff url(/article-h5/static/img/book-border.3ca9453.png) repeat-y center center;
-webkit-transform: rotateY(35deg);
transform: rotateY(35deg);
-webkit-transform-origin: left center;
transform-origin: left center;
}
.preload {
position: fixed;
z-index: -999;
left: 0;
top: 0;
opacity: 0;
width: 0;
height: 0;
overflow: hidden;
}
.page-content {
position: absolute;
left: 0;
bottom: 0;
right: 0;
height: 0.96rem;
padding: 0.46rem 0.14rem 0;
background-image: -webkit-gradient(linear, left bottom, left top, from(rgba(0, 0, 0, 0.92)), to(rgba(0, 0, 0, 0.00)));
background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.92) 0%, rgba(0, 0, 0, 0.00) 100%);
pointer-events: none;
width: 100%;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.page-content-type-1 {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
.page-content-type-2 {
text-align: center;
}
.page-content .desc {
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
width: calc(65% - 0.14rem);
}
.page-content .name {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-family: PingFangSC-Semibold;
font-size: 0.16rem;
color: #FFFFFF;
line-height: 1;
}
.page-content .price {
font-family: PingFangSC-Semibold;
font-size: 0.14rem;
color: #FFFFFF;
}
.page-content .buy-button {
width: 35%;
text-align: right;
}
.page-content .buy-button button {
display: inline-block;
height: 0.34rem;
max-width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
border: none;
padding: 0 0.16rem;
background: #FF8D00;
-webkit-box-shadow: 0 0.02rem 0.04rem 0 rgba(0, 0, 0, 0.20);
box-shadow: 0 0.02rem 0.04rem 0 rgba(0, 0, 0, 0.20);
border-radius: 0.02rem;
font-family: PingFangSC-Semibold;
font-size: 0.14rem;
color: #FFFFFF;
pointer-events: auto;
}
.page-content .form-button {
height: 0.34rem;
border: none;
padding: 0 0.16rem;
background: #FFFFFF;
-webkit-box-shadow: 0 0.02rem 0.04rem 0 rgba(0, 0, 0, 0.20);
box-shadow: 0 0.02rem 0.04rem 0 rgba(0, 0, 0, 0.20);
border-radius: 0.02rem;
font-family: PingFangSC-Semibold;
font-size: 0.14rem;
color: #0084BF;
pointer-events: auto;
}
</style>

2
src/index.js Normal file
View File

@ -0,0 +1,2 @@
import flipPage from './App.vue'
export default flipPage;

708
src/turn_controller.js Normal file
View File

@ -0,0 +1,708 @@
import TurnPage from "./turn_page.vue";
const PI = Math.PI;
const A90 = PI / 2;
function bezier(p1, p2, p3, p4, t) {
const a = 1 - t;
const b = a * a * a;
const c = t * t * t;
return point2D(Math.round(b * p1.x + 3 * t * a * a * p2.x + 3 * t * t * a * p3.x + c * p4.x), Math.round(b * p1.y + 3 * t * a * a * p2.y + 3 * t * t * a * p3.y + c * p4.y));
}
function point2D(x, y) {
return { x, y };
}
function peelingPoint(a, b, c) {
return {
corner: a,
x: b,
y: c
};
}
function _startPoint(corner, width, height, opts) {
opts = opts || 0;
switch (corner) {
case "tl":
return point2D(opts, opts);
case "tr":
return point2D(width - opts, opts);
case "bl":
return point2D(opts, height - opts);
case "br":
return point2D(width - opts, height - opts);
case "l":
return point2D(opts, 0);
case "r":
return point2D(width - opts, 0);
}
}
function _endPoint(corner, width, height) {
switch (corner) {
case "tl":
return point2D(width * 2, 0);
case "tr":
return point2D(-width, 0);
case "bl":
return point2D(width * 2, height);
case "br":
return point2D(-width, height);
case "l":
return point2D(width * 2, 0);
case "r":
return point2D(-width, 0);
}
}
function _translate(x, y) {
return " translate3d(" + x + "px," + y + "px, 0px) ";
}
function _rotate(degrees) {
return " rotate(" + degrees + "deg) ";
}
function _scale(a, b) {
return " scale3d(" + a + "," + b + ", 1) ";
}
function _deg(radians) {
return radians / PI * 180;
}
function _transform(transform, origin) {
const properties = {};
if (origin) properties["transform-origin"] = origin;
properties["transform"] = transform;
return properties;
}
function fold(point, width, height, startPoint) {
let c, d, e, px, gradientSize, h, far, j, k, l, m, n, q;
let w = 0;
let mv = point2D(0, 0);
let df = point2D(0, 0);
let tr = point2D(0, 0);
const B = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2), 2);
const compute = function() {
c = _startPoint(point.corner, width, height);
const k = width - c.x - point.x;
const l = c.y - point.y;
let alpha = Math.atan2(l, k);
let distance = Math.sqrt(k * k + l * l);
const q = point2D(width - c.x - Math.cos(alpha) * width, c.y - Math.sin(alpha) * width);
if (distance > width) {
point.x = q.x;
point.y = q.y;
}
const rel = point2D(0, 0);
const middle = point2D(0, 0);
rel.x = c.x ? c.x - point.x : point.x;
rel.y = c.y ? c.y - point.y : point.y;
middle.x = e ? width - rel.x / 2 : point.x + rel.x / 2;
middle.y = rel.y / 2;
alpha = A90 - Math.atan2(rel.y, rel.x);
const gamma = alpha - Math.atan2(middle.y, middle.x);
distance = Math.sin(gamma) * Math.sqrt(middle.x * middle.x + middle.y * middle.y);
tr = point2D(distance * Math.sin(alpha), distance * Math.cos(alpha));
if (alpha > A90) {
tr.x = tr.x + Math.abs(tr.y * rel.y / rel.x);
tr.y = 0;
if (Math.round(tr.x * Math.tan(PI - alpha)) < height) {
point.y = Math.sqrt(Math.pow(height, 2) + 2 * middle.x * rel.x);
if (d) point.y = height - point.y;
return compute();
}
const D = PI - alpha;
const E = B - height / Math.sin(D);
mv = point2D(Math.round(E * Math.cos(D)), Math.round(E * Math.sin(D)));
if (e) mv.x = -mv.x;
if (d) mv.y = -mv.y;
}
w = Math.round(100 * _deg(alpha)) / 100;
px = Math.round(tr.y / Math.tan(alpha) + tr.x);
const side = width - px;
let G = Math.min(height, side * Math.tan(alpha));
if (G < 0) G = height;
const sideX = side * Math.cos(2 * alpha);
const sideY = side * Math.sin(2 * alpha);
df = point2D(Math.round(e ? side - sideX : px + sideX), Math.round(d ? sideY : height - sideY));
const endingPoint = _endPoint(point.corner, width, height);
far = Math.sqrt(Math.pow(endingPoint.x - point.x, 2) + Math.pow(endingPoint.y - point.y, 2)) / width;
gradientSize = Math.min(100, side * Math.sin(alpha));
h = 1.3 * Math.min(side, G);
tr.x = Math.round(tr.x);
tr.y = Math.round(tr.y);
return true;
};
const transform = function(tr, c, x, a) {
const l = ["0", "auto"];
const mvW = (width - B) * x[0] / 100;
const mvH = (height - B) * x[1] / 100;
const cssA = {
left: l[c[0]],
top: l[c[1]],
right: l[c[2]],
bottom: l[c[3]]
};
const aliasingFk = a !== 90 && a !== -90 ? e ? -1 : 1 : 0;
const origin = x[0] + "% " + x[1] + "%";
const styles = [];
styles.push({ ...cssA, ..._transform(_rotate(a) + _translate(tr.x + aliasingFk, tr.y), origin) });
styles.push({ ...cssA, ..._transform(_rotate(a) + _translate(tr.x + df.x - mv.x - width * x[0] / 100, tr.y + df.y - mv.y - height * x[1] / 100) + _rotate(Math.round(100 * (180 / a - 2) * a) / 100), origin) });
styles.push(_transform(_translate(-tr.x + mvW - aliasingFk, -tr.y + mvH) + _rotate(-a), origin));
styles.push(_transform(_translate(width - tr.x + mv.x + mvW, -tr.y + mv.y + mvH) + _rotate(-a), origin));
let z, C, D;
if (d) {
if (e) {
C = a - 90;
D = px - 50;
gradientSize = -gradientSize;
z = "50% 25%";
} else {
C = a - 270;
D = width - px - 50;
z = "50% 25%";
}
} else {
if (e) {
D = px - 50;
C = a - 270;
gradientSize = -gradientSize;
z = "50% 75%";
} else {
D = width - px - 50;
C = a - 90;
z = "50% 75%";
}
}
let E = Math.max(0.5, 2 - far);
if (E > 1) E = E >= 1.7 ? (2 - E) / 0.3 : 1;
styles.push({ opacity: Math.round(100 * E) / 100, ..._transform(_translate(D, 0) + _rotate(C) + _scale(gradientSize / 100, 1), z) });
if (d) {
if (e) {
C = -270 - a;
h = -h;
D = width - px - 20;
z = "20% 25%";
} else {
C = -90 - a;
D = px - 20;
z = "20% 25%";
}
} else {
if (e) {
C = -90 - a;
D = width - px - 20;
h = -h;
z = "20% 75%";
} else {
C = 90 - a;
D = px - 20;
z = "20% 75%";
}
}
E = far < 0.3 ? far / 0.3 : 1;
styles.push({ opacity: Math.round(100 * E) / 100, ..._transform(_translate(D, 0) + _rotate(C) + _scale(-h / 100, 1), z) });
return styles;
};
switch (point.corner) {
case "l":
m = point.y - startPoint.y;
n = point.x;
q = Math.atan2(m, n);
if (q > 0) {
j = startPoint.y;
k = Math.sqrt(n * n + m * m);
l = 2 * j * Math.sin(q) + k;
point.x = l * Math.cos(q);
point.y = l * Math.sin(q);
point.corner = "tl";
e = true;
d = true;
compute();
return transform(tr, [1, 0, 0, 1], [100, 0], w);
} else {
q = -q;
j = height - startPoint.y;
k = Math.sqrt(n * n + m * m);
l = 2 * j * Math.cos(A90 - q) + k;
point.x = l * Math.cos(q);
point.y = height - l * Math.sin(q);
point.corner = "bl";
e = true;
compute();
return transform(point2D(tr.x, -tr.y), [1, 1, 0, 0], [100, 100], -w);
}
case "r":
m = startPoint.y - point.y;
n = width - point.x;
q = Math.atan2(m, n);
if (q < 0) {
j = startPoint.y;
q = -q;
k = Math.sqrt(n * n + m * m);
l = 2 * j * Math.sin(q) + k;
point.x = width - l * Math.cos(q);
point.y = l * Math.sin(q);
point.corner = "tr";
d = true;
compute();
return transform(point2D(-tr.x, tr.y), [0, 0, 0, 1], [0, 0], -w);
} else {
j = height - startPoint.y;
k = Math.sqrt(n * n + m * m);
l = 2 * j * Math.cos(A90 - q) + k;
point.x = width - l * Math.cos(q);
point.y = height - l * Math.sin(q);
point.corner = "br";
compute();
return transform(point2D(-tr.x, -tr.y), [0, 1, 1, 0], [0, 100], w);
}
case "tl":
d = true;
e = true;
point.x = Math.max(point.x, 1);
c = _startPoint("tl", width, height);
compute();
return transform(tr, [1, 0, 0, 1], [100, 0], w);
case "tr":
d = true;
point.x = Math.min(point.x, width - 1);
compute();
return transform(point2D(-tr.x, tr.y), [0, 0, 0, 1], [0, 0], -w);
case "bl":
e = true;
point.x = Math.max(point.x, 1);
compute();
return transform(point2D(tr.x, -tr.y), [1, 1, 0, 0], [100, 100], -w);
case "br":
point.x = Math.min(point.x, width - 1);
compute();
return transform(point2D(-tr.x, -tr.y), [0, 1, 1, 0], [0, 100], w);
}
}
function animatef(point) {
if (!point.to.length) point.to = [point.to];
if (!point.from.length) point.from = [point.from];
const diff = [];
const len = point.to.length;
const time = Date.now();
const frame = function() {
const v = [];
const timeDiff = Math.min(point.duration, Date.now() - time);
for (let i = 0; i < len; i++) v.push(easing(1, timeDiff, point.from[i], diff[i], point.duration));
point.frame(len === 1 ? v[0] : v);
if (timeDiff === point.duration) {
if (point.complete) point.complete();
} else {
requestAnimationFrameId = requestAnimationFrame(frame);
}
};
for (let i = 0; i < len; i++) diff.push(point.to[i] - point.from[i]);
const easing = function(x, t, b, c, data) {
return c * Math.sqrt(1 - (t = t / data - 1) * t) + b;
};
let requestAnimationFrameId;
frame();
return {
stop() {
if (point.complete) point.complete();
cancelAnimationFrame(requestAnimationFrameId);
}
};
}
function turnPage(p1, duration, width, height, frame, complete) {
const p4 = _endPoint(p1.corner, width, height);
return animatef({
from: 0,
to: 1,
frame: function(v) {
const np = bezier(p1, p1, p4, p4, v);
frame({ x: np.x, y: np.y, corner: p1.corner });
},
complete: function() {
complete();
},
duration: duration
});
}
function hideFoldedPage(p1, width, height, frame, complete) {
const p4 = _startPoint(p1.corner, width, height);
const top = p1.corner.substr(0, 1) === "t";
const delta = top ? Math.min(0, p1.y - p4.y) / 2 : Math.max(0, p1.y - p4.y) / 2;
const p2 = point2D(p1.x, p1.y + delta);
const p3 = point2D(p4.x, p4.y - delta);
return animatef({
from: 0,
to: 1,
frame: function(v) {
const np = bezier(p1, p2, p3, p4, v);
frame({ x: np.x, y: np.y, corner: p1.corner });
},
complete: function() {
complete();
},
duration: 600
});
}
function showFoldedPage(to, width, height, frame, complete) {
const point = _startPoint(to.corner, width, height, 1);
return animatef({
from: [point.x, point.y],
to: [to.x, to.y],
duration: 300,
frame: function(v) {
const x = Math.round(v[0]);
const y = Math.round(v[1]);
frame({ x, y, corner: to.corner });
},
complete: function() {
complete();
}
});
}
export default {
components: { TurnPage },
props: {
data: Array,
width: {
type: Number,
default: 0
},
height: {
type: Number,
default: 0
}
},
data() {
return {
defaultStyles: [{}, {}, {}, {}, {}, {}],
startPoint: null,
relPoint: null,
invalidTouch: false,
action: null,
runAnimation: null,
viewIndex: 0,
turnPage: null,
backPage: 0,
turnActive: false,
showLastCoverPage: false,
touchTimeline: [],
styles: [{}, {}, {}, {}, {}, {}],
isStart: false
};
},
mounted() {
},
activated() {
this.$nextTick(() => {
this.TouchMove();
});
},
deactivated() {
this.$nextTick(() => {
this.TouchMoveOut();
});
},
methods: {
TouchMove() {
if (this.fun.getPhoneEnv() == 3) {
this.$refs.turn.addEventListener("mousemove", this.handleManualTouchMove, false);
this.$refs.turn.addEventListener("mousedown", this.handleManualTouchStart, false);
this.$refs.turn.addEventListener("mouseup", this.handleManualTouchEnd, false);
} else {
this.$refs.turn.addEventListener("touchmove", this.handleManualTouchMove, false);
this.$refs.turn.addEventListener("touchstart", this.handleManualTouchStart, false);
this.$refs.turn.addEventListener("touchend", this.handleManualTouchEnd, false);
}
},
TouchMoveOut() {
if (this.fun.getPhoneEnv() == 3) {
this.$refs.turn.removeEventListener("mousemove", this.handleManualTouchMove, false);
this.$refs.turn.removeEventListener("mousedown", this.handleManualTouchStart, false);
this.$refs.turn.removeEventListener("mouseup", this.handleManualTouchEnd, false);
} else {
this.$refs.turn.removeEventListener("touchmove", this.handleManualTouchMove, false);
this.$refs.turn.removeEventListener("touchstart", this.handleManualTouchStart, false);
this.$refs.turn.removeEventListener("touchend", this.handleManualTouchEnd, false);
}
},
handleManualTouchStart(e) {
this.isStart = true;
let x = "";
let y = "";
if (this.runAnimation) {
this.runAnimation.stop();
this.updateTurn();
}
const { width, height, viewIndex } = this;
if (this.fun.getPhoneEnv() == 3) {
x = e.clientX - (window.innerWidth - width) / 2;
y = e.clientY - (window.innerHeight - height) / 2;
} else {
x = e.touches[0].clientX - (window.innerWidth - width) / 2;
y = e.touches[0].clientY - (window.innerHeight - height) / 2;
}
this.startPoint = peelingPoint("r", x, y);
this.touchTimeline = [{ t: Date.now(), x }];
if (x < width / 2) {
this.action = "backward";
if (viewIndex <= 0) {
this.invalidTouch = true;
return;
}
this.turnPage = viewIndex - 1;
this.backPage = viewIndex;
} else {
this.action = "forward";
if (viewIndex >= this.data.length - 1) {
this.invalidTouch = true;
return;
}
this.turnPage = viewIndex;
this.backPage = viewIndex + 1;
}
// console.log(x,this.touchTimeline)
this.readyTurn();
},
handleManualTouchMove(e) {
let x = "";
let y = "";
if (this.isStart) {
const { width, height } = this;
if (this.fun.getPhoneEnv() == 3) {
x = e.clientX - (window.innerWidth - width) / 2;
y = e.clientY - (window.innerHeight - height) / 2;
} else {
x = e.touches[0].clientX - (window.innerWidth - width) / 2;
y = e.touches[0].clientY - (window.innerHeight - height) / 2;
}
const corner = this.startPoint.corner;
if (this.action === "forward" && this.startPoint.x < x) return;
if (this.action === "backward" && this.startPoint.x > x) return;
this.touchTimeline.push({ t: Date.now(), x });
const point = peelingPoint(corner, x, y);
if (this.invalidTouch) return;
this.updateTurn(fold(point, width, height, this.startPoint));
this.relPoint = point;
this.turnActive = true;
}
},
handleManualTouchEnd() {
this.isStart = false;
const action = this.action;
if (this.touchTimeline.length < 2) {
if (this.touchTimeline.length) {
this.handleManualTaped();
} else {
this.turnPage = null;
this.backPage = this.viewIndex;
}
this.invalidTouch = false;
this.touchTimeline = [];
return;
}
if (this.invalidTouch) {
if (action === "forward" && this.isSwipe(action)) {
// this.showLastCoverPage = true
this.$emit("next", { to: this.viewIndex + 1, from: this.viewIndex });
}
if (action === "backward" && this.isSwipe(action)) {
this.$emit("prev", { to: this.viewIndex - 1, from: this.viewIndex });
}
} else {
if (action === "forward") {
if (this.isSwipe(action)) {
this.toPage(this.viewIndex + 1, this.relPoint);
} else {
this.hideFolded(this.relPoint, () => {
});
}
}
if (action === "backward") {
if (this.isSwipe(action)) {
this.toPage(this.viewIndex - 1, this.relPoint);
} else {
this.turn(this.relPoint, () => {
});
}
}
}
this.invalidTouch = false;
this.touchTimeline = [];
},
handleManualTaped() {
const { width } = this;
const x = this.touchTimeline[0].x;
this.action = null;
if (x < width / 4) return this.toPage(this.viewIndex - 1);
if (x > width / 4 * 3) return this.toPage(this.viewIndex + 1);
this.$emit("tap");
},
readyTurn() {
const { width, height } = this;
const point = this.action === "forward" ? { x: width, y: 0 } : { x: -width, y: 0 };
const fromPoint = peelingPoint("tr", point.x, point.y);
// console.log(fromPoint,"fromPoint");
this.updateTurn(fold(fromPoint, width, height));
},
updateTurn(style = [{}, {}, {}, {}, {}, {}]) {
this.styles = style;
},
isSwipe(action) {
const first = this.touchTimeline[0];
const last = this.touchTimeline[this.touchTimeline.length - 1];
const swipeTime = Date.now() - first.t;
const minTime = 200;
const minDistance = 20;
if (action === "forward") return first.x - last.x > 50 || (first.x - last.x > minDistance && swipeTime < minTime);
if (action === "backward") return last.x - first.x > 50 || (last.x - first.x > minDistance && swipeTime < minTime);
return false;
},
toPage(toPage, startPoint) {
const fromPage = this.viewIndex;
if (toPage > fromPage) this.$emit("next", { to: toPage, from: fromPage });
if (toPage < fromPage) this.$emit("prev", { to: toPage, from: fromPage });
if (toPage === fromPage || toPage < 0 || toPage > this.data.length - 1) return;
if (this.runAnimation) {
this.runAnimation.stop();
}
const { width, height } = this;
const point = toPage > fromPage ? { x: width, y: height - 45 } : { x: -width, y: 45 };
const fromPoint = peelingPoint("br", point.x, point.y);
if (toPage > fromPage) {
if (!this.action) {
this.action = "forward";
this.turnPage = fromPage;
this.backPage = toPage;
this.turnActive = true;
}
this.turn(startPoint || fromPoint, () => {
this.viewIndex = toPage;
this.turnPage = null;
this.backPage = toPage;
// console.log('完成翻页,当前页', this.backPage)
this.updateTurn();
this.turnActive = false;
this.$emit("change", this.backPage);
});
} else {
if (!this.action) {
this.action = "backward";
this.turnPage = toPage;
this.backPage = fromPage;
}
this.hideFolded(startPoint || fromPoint, () => {
this.viewIndex = toPage;
this.turnPage = null;
this.backPage = toPage;
// console.log('完成翻页,当前页', this.backPage)
this.updateTurn();
this.turnActive = false;
this.$emit("change", this.backPage);
});
}
// console.log('this.turnActive', this.turnPage, this.turnActive)
this.$emit("turning", this.backPage);
},
turn(fromPoint, complete) {
const { width, height } = this;
this.runAnimation = turnPage(fromPoint, 600, width, height, point => {
this.updateTurn(fold(point, width, height));
}, () => {
complete();
this.action = null;
this.turnPage = null;
this.backPage = this.viewIndex;
this.updateTurn();
this.runAnimation = null;
});
},
hideFolded(fromPoint, complete) {
const { width, height } = this;
this.runAnimation = hideFoldedPage(fromPoint, width, height, point => {
this.updateTurn(fold(point, width, height));
}, () => {
complete();
this.action = null;
this.turnPage = null;
this.backPage = this.viewIndex;
this.updateTurn();
this.turnActive = false;
this.runAnimation = null;
});
},
handleCoverTouchStart(e) {
const x = e.touches[0].clientX;
this.touchTimeline = [{ t: Date.now(), x }];
},
handleCoverTouchMove(e) {
const x = e.touches[0].clientX;
this.touchTimeline.push({ t: Date.now(), x });
},
handleCoverTouchEnd(e) {
const first = this.touchTimeline[0];
const last = this.touchTimeline[this.touchTimeline.length - 1];
if (last.x - first.x > 20 && last.t - first.t < 200) {
this.showLastCoverPage = false;
}
}
}
};

325
src/turn_page.vue Normal file
View File

@ -0,0 +1,325 @@
<template>
<div class="turn-wraper"
:style="{width: width+'px', height: height+'px', overflow: active ? 'visible' : 'hidden', zIndex: length - index}">
<div class="turn-page-left" :style="{left: '-'+width+'px'}">
<div class="turn-page-left-clip" :style="(Object.assign({}, {width: clipSize+'px', height: clipSize+'px'}, styles[3]))">
<div class="turn-page-left-content"
:style="(Object.assign({}, {width: width+'px', height: height+'px'}, styles[1]))">
<div class="turn-page-left-inner">
<div class="manual-item">
<div class="page-count">{{index + 1}} / {{length}}</div>
<div class="manual-page">
<div class="page-photo">
<img :src="item.picture_image">
</div>
<!--<div class="page-content page-content-type-1">-->
<!--<div class="desc"><h4 class="name">-->
<!--小程序组件小程序组件小程序组件小程序组件小程序组件小程序组件</h4>-->
<!--<div class="price">¥19800</div> &lt;!&ndash;&ndash;&gt; &lt;!&ndash;&ndash;&gt;</div>-->
<!--<div class="buy-button">-->
<!--<button type="button">查看详情</button>-->
<!--</div>-->
<!--</div>-->
</div>
</div>
</div>
<div class="turn-page-left-gradient" :style="(Object.assign({}, {top: ('-' + (height / 2)+'px'), height: (height* 2)+'px'}, styles[4]))"></div>
</div>
</div>
</div>
<div class="turn-page-right" :style="{width: width+'px', height: height+'px'}">
<div class="turn-page-right-gradient" :style="(Object.assign({}, {top: ('-' + (height / 2)+'px'), height: (height* 2)+'px'}, styles[5]))"></div>
<div class="turn-page-right-clip" :style="(Object.assign({}, {width: clipSize+'px', height: clipSize+'px'}, styles[2]))">
<div class="turn-page-right-content" :style="(Object.assign({}, {width: width+'px', height: height+'px'}, styles[0]))">
<div class="manual-item">
<div class="page-count">{{index + 1}} / {{length}}</div>
<div class="manual-page">
<div class="page-photo">
<img :src="item.picture_image">
</div>
<!--<div class="page-content page-content-type-1">-->
<!--<div class="desc"><h4 class="name">-->
<!--小程序组件小程序组件小程序组件小程序组件小程序组件小程序组件</h4>-->
<!--<div class="price">¥19800</div> &lt;!&ndash;&ndash;&gt; &lt;!&ndash;&ndash;&gt;</div>-->
<!--<div class="buy-button">-->
<!--<button type="button">查看详情</button>-->
<!--</div>-->
<!--</div>-->
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import turn_page_controller from "./turn_page_controller";
export default turn_page_controller;
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
.turn-wraper {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
pointer-events: none;
-webkit-transition: all ease 0.35s;
transition: all ease 0.35s;
}
.turn-page-left {
position: absolute;
top: 0;
z-index: 5;
width: 100%;
height: 100%;
overflow: visible;
}
.turn-page-left-clip {
position: absolute;
top: 0;
left: 0;
overflow: hidden;
}
.turn-page-left-content {
position: absolute;
overflow: hidden;
background-color: #fff;
}
.turn-page-left-inner {
width: 100%;
height: 100%;
-webkit-transform: rotateY(180deg);
transform: rotateY(180deg);
opacity: 0.3;
-webkit-transition: width all ease 0.35s, height all ease 0.35s;
transition: width all ease 0.35s, height all ease 0.35s;
}
.turn-page-left-gradient {
position: absolute;
z-index: 99;
width: 100px;
-webkit-transform: scale3d(0, 1, 1);
transform: scale3d(0, 1, 1);
background: -webkit-gradient(linear, 0% 0%, 100% 0%, from(rgba(0, 0, 0, 0)), color-stop(0.3, rgba(0, 0, 0, 0.2)), color-stop(0.5, rgba(0, 0, 0, 0.5)));
}
.turn-page-right {
position: absolute;
top: 0;
left: 0;
z-index: 4;
overflow: hidden;
}
.turn-page-right-gradient {
position: absolute;
z-index: 2;
width: 100px;
opacity: 1;
-webkit-transform: scale3d(0, 1, 1);
transform: scale3d(0, 1, 1);
background: -webkit-gradient(linear, 0% 0%, 100% 0%, from(rgba(0, 0, 0, 0)), color-stop(0.2, rgba(0, 0, 0, 0.3)), color-stop(0.2, rgba(0, 0, 0, 0.4)), color-stop(0.4, rgba(0, 0, 0, 0.1)), to(rgba(0, 0, 0, 0)));
}
.turn-page-right-clip {
position: absolute;
top: 0;
left: 0;
overflow: hidden;
z-index: auto;
}
.turn-page-right-content {
position: absolute;
overflow: hidden;
z-index: 1;
transition: width all ease 0.35s, height all ease 0.35s;
}
.manual-item {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #fff;
-webkit-transition: all ease 0.35s;
transition: all ease 0.35s;
}
.page-count {
position: absolute;
top: 0.8rem;
left: 50%;
z-index: 1;
padding: 0 0.8rem;
background: rgba(0, 0, 0, 0.8);
border-radius: 0.5rem;
font-size: 14px;
color: #FFFFFF;
text-align: center;
line-height: 1.8;
-webkit-transform: translate(-50%, 0);
transform: translate(-50%, 0);
}
.manual-page {
position: relative;
width: 100%;
height: 100%;
display: -webkit-box;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.manual-page.loading {
/*background: url(/article-h5/static/img/loading.95eeac7.gif) no-repeat center center !important;*/
/*background-size: 60px !important;*/
}
.page-photo {
position: relative;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
font-size: 0;
line-height: 0;
width: 100%;
height: 100%;
opacity: 1;
-webkit-transition: opacity ease 0.2s;
transition: opacity ease 0.2s;
}
.manual-page.loading .page-photo {
opacity: 0;
}
.page-photo::after {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 100%;
z-index: 2;
pointer-events: none;
background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 0.2)), color-stop(6%, rgba(255, 255, 255, 0.15)), to(rgba(255, 255, 255, 0.15)));
background-image: linear-gradient(90deg, rgba(0, 0, 0, 0.2) 0%, rgba(255, 255, 255, 0.15) 6%, rgba(255, 255, 255, 0.15) 100%);
}
.page-photo img {
display: inline-block;
max-width: 100%;
max-height: 100%;
}
.page-content {
position: absolute;
left: 0;
bottom: 0;
right: 0;
height: 3rem;
line-height: 1rem;
text-align: left;
padding: 0.6rem 0.2rem;
background-image: -webkit-gradient(linear, left bottom, left top, from(rgba(0, 0, 0, 0.92)), to(rgba(0, 0, 0, 0.00)));
background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.92) 0%, rgba(0, 0, 0, 0.00) 100%);
pointer-events: none;
width: 100%;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.page-content-type-1 {
display: -webkit-box;
display: flex;
}
.page-content-type-2 {
text-align: center;
}
.page-content .desc {
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
width: calc(65% - 0.14rem);
}
.page-content .name {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-family: PingFangSC-Semibold;
font-size: 14px;
color: #FFFFFF;
line-height: 1;
}
.page-content .price {
font-family: PingFangSC-Semibold;
font-size: 14px;
color: #FFFFFF;
}
.page-content .buy-button {
width: 35%;
text-align: right;
}
.page-content .buy-button button {
display: inline-block;
height: 2rem;
max-width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
border: none;
padding: 0 0.16rem;
background: #FF8D00;
-webkit-box-shadow: 0 0.02rem 0.04rem 0 rgba(0, 0, 0, 0.20);
box-shadow: 0 0.02rem 0.04rem 0 rgba(0, 0, 0, 0.20);
border-radius: 0.02rem;
font-family: PingFangSC-Semibold;
font-size: 14px;
color: #FFFFFF;
pointer-events: auto;
}
.page-content .form-button {
height: 0.34rem;
border: none;
padding: 0 0.16rem;
background: #FFFFFF;
-webkit-box-shadow: 0 0.02rem 0.04rem 0 rgba(0, 0, 0, 0.20);
box-shadow: 0 0.02rem 0.04rem 0 rgba(0, 0, 0, 0.20);
border-radius: 0.02rem;
font-family: PingFangSC-Semibold;
font-size: 14px;
color: #0084BF;
pointer-events: auto;
}
</style>

View File

@ -0,0 +1,36 @@
export default {
props: {
item: Object,
index: Number,
width: {
type: Number,
default: 0
},
height: {
type: Number,
default: 0
},
length: {
type: Number,
default: 0
},
active: { // 翻动效果生效
type: Boolean,
default: false
},
styles: {
type: Array,
default: () => [{}, {}, {}, {}, {}, {}]
}
},
data() {
return {};
},
mounted() {
},
computed: {
clipSize() {
return Math.sqrt(Math.pow(this.width, 2) + Math.pow(this.height, 2), 2);
}
}
};

54
webpack.config.js Normal file
View File

@ -0,0 +1,54 @@
const path = require("path");
const webpack = require("webpack");
const uglify = require("uglifyjs-webpack-plugin");
module.exports = {
devtool: 'source-map',
entry: "./src/index.js",//入口文件就是上步骤的src目录下的index.js文件
output: {
path: path.resolve(__dirname, './dist'),//输出路径就是上步骤中新建的dist目录
publicPath: '/dist/',
filename: 'flip.min.js',
libraryTarget: 'umd',
umdNamedDefine: true
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.scss/,
use: [
{loader: "style-loader"},
{loader: "css-loader"},
{loader: "sass-loader"}
]
},
{
test: /\.js$/,
exclude: /node_modules/,
options:{
presets:["es2015"]
},
loader: 'babel-loader'
},
{
test: /\.(png|jpg|gif|ttf|svg|woff|eot)$/,
loader: 'url-loader',
query: {
limit: 30000,
name: '[name].[ext]?[hash]'
}
}
]
},
plugins: [
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify("production")
}
})
]
};