欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Vue开发Web阅读器(一)

程序员文章站 2022-06-20 14:24:16
...

本项目使用vue3

初始化项目后需配置vue.config.js,官方文档:https://cli.vuejs.org/zh/config/#vue-config-js

我的暂时配置:

const path = require('path')

function resolve (dir) {
  return path.join(__dirname, dir)
}

module.exports = {
  chainWebpack: (config) => {
    config.resolve.alias
      .set('src', resolve('src'))
      .set('styles', resolve('src/assets/styles'))
      .set('components', resolve('src/components'))
  },
  devServer: {
    host: '0.0.0.0',
    port: 8080
  },
  publicPath: './'
}

安装sass依赖和阅读器引擎

npm i node-sass sass-loader --save-dev

npm i epubjs --save

将项目所需的字体图标放入assets下的styles文件夹,将icon.css导入main.js中

Vue开发Web阅读器(一)

import 'styles/icon.css'

index.html中配置手机访问时不能缩放

<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">

rem配置

在App.vue中配置,当DOM内容加载时设置字体大小为屏幕宽度的1/10,当字体大小大于50px时,还是50px

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'app',
  components: {
    HelloWorld
  }
}

document.addEventListener('DOMContentLoaded', () => {
  let html = document.querySelector('html')
  let fontSize = window.innerWidth / 10
  fontSize = fontSize > 50 ? 50 : fontSize
  html.style.fontSize = fontSize + 'px'
})
</script>

reset.scss和全局样式global.scss配置

访问https://meyerweb.com/eric/tools/css/reset/复制到styles/reset.scss中

还要加入html和body的默认设置

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
  margin: 0;
  padding: 0;
  border: 0;
  font-size: 100%;
  font: inherit;
  vertical-align: baseline;
}

/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
  display: block;
}

body {
  line-height: 1;
}

ol, ul {
  list-style: none;
}

blockquote, q {
  quotes: none;
}

blockquote:before, blockquote:after,
q:before, q:after {
  content: '';
  content: none;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
}

html, body {
  width: 100%;
  height: 100%;
  font-family: 'PingFangSC-Light', 'PingFang SC', 'STHeitiSC-Light', 'Helvetica-Light', 'Arial', 'sans-serif';
}

配置global.scss

@import "reset.scss";

// 1rem = fontSize px
// 1px = 1/fontSize rem

$fontSize: 37.5;
@function px2rem($px) {
  @return ($px / $fontSize) + rem
}

@mixin center() {
  display: flex;
  justify-content: center;
  align-items: center;
}

然后在main.js中引入global.scss

Vue开发Web阅读器(一)

通过epubjs将epub电子书解析成Book对象,再通过Book对象生成Rendition对象

 

在src目录下新建Ebook.vue,router.js中配置路由和重定向,当访问主页面时重定向到/ebook页面

import Vue from 'vue'
import Router from 'vue-router'

import Ebook from '@/Ebook'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      redirect: '/ebook'
    },
    {
      path: '/ebook',
      component: Ebook
    }
  ]
})

Ebook.vue中写入

<template>
  <div>aaa</div>
</template>

<script>
import Epub from 'epubjs'

const DOWNLOAD_URL = '/static/2018_Book_AgileProcessesInSoftwareEngine.epub'
export default {
  methods: {
    // 电子书解析和渲染
    showEpub () {
      // 生成Ebook
      // 生成Rendition,通过Book.renderTo方法生成
      // 通过Rendition.display渲染电子书
    }
  },
  mounted () {
    this.showEpub()
  }
}
</script>
<template>
  <div class="ebook">
    <div class="read-wrapper">
      <div id="read"></div>
    </div>
  </div>
</template>

<script>
import Epub from 'epubjs'

const DOWNLOAD_URL = '/2018_Book_AgileProcessesInSoftwareEngine.epub'
global.ePub = Epub
export default {
  methods: {
    // 电子书解析和渲染
    showEpub () {
      // 生成Ebook
      this.book = new Epub(DOWNLOAD_URL)
      // 生成Rendition,通过Book.renderTo方法生成
      this.rendition = this.book.renderTo('read', {
        width: window.innerWidth,
        height: window.innerHeight
      })
      // 通过Rendition.display渲染电子书
      this.rendition.display()
    }
  },
  mounted () {
    this.showEpub()
  }
}
</script>

设置遮罩,点击左边或右边遮罩来控制翻页

<template>
  <div class="ebook">
    <div class="read-wrapper">
      <div id="read" class="read"></div>
      <div class="mask">
        <div class="left" @click="prevPage"></div>
        <div class="center"></div>
        <div class="right" @click="nextPage"></div>
      </div>
    </div>
  </div>
</template>

<script>
import Epub from 'epubjs'

const DOWNLOAD_URL = '/2018_Book_AgileProcessesInSoftwareEngine.epub'
global.ePub = Epub
export default {
  methods: {
    // 电子书解析和渲染
    showEpub () {
      // 生成Ebook
      this.book = new Epub(DOWNLOAD_URL)
      // 生成Rendition,通过Book.renderTo方法生成
      this.rendition = this.book.renderTo('read', {
        width: window.innerWidth,
        height: window.innerHeight
      })
      // 通过Rendition.display渲染电子书
      this.rendition.display()
    },
    prevPage () {
      if (this.rendition) {
        this.rendition.prev()
      }
    },
    nextPage () {
      if (this.rendition) {
        this.rendition.next()
      }
    }
  },
  mounted () {
    this.showEpub()
  }
}
</script>

<style lang="scss" scoped>
  @import "~styles/global";

  .ebook {
    position: relative;
    .read-wrapper {
      .read {
        /*width: 500px;*/
        /*margin: 0 auto;*/
      }
      .mask {
        display: flex;
        position: absolute;
        left: 0;
        top: 0;
        width: 100%;
        height: 100%;
        z-index: 100;
        .left {
          width: 100px;
        }
        .right {
          width: 100px;
        }
        .center {
          flex: 1;
        }
      }
    }
  }
</style>

添加菜单,添加点击中心处显示和隐藏菜单事件

<template>
  <div class="ebook">
    <transition name="slide-down">
      <div v-show="isMenuShow" class="title-wrapper">
        <div class="left">
          <span class="icon-back icon"></span>
        </div>
        <div class="right">
          <div class="icon-wrapper">
            <span class="icon-cart icon"></span>
          </div>
          <div class="icon-wrapper">
            <span class="icon-person icon"></span>
          </div>
          <div class="icon-wrapper">
            <span class="icon-more icon"></span>
          </div>
        </div>
      </div>
    </transition>
    <div class="read-wrapper">
      <div id="read" class="read"></div>
      <div class="mask">
        <div class="left" @click="prevPage"></div>
        <div class="center" @click="toggle"></div>
        <div class="right" @click="nextPage"></div>
      </div>
    </div>
    <transition name="slide-up">
      <div v-show="isMenuShow" class="menu-wrapper">
        <div class="icon-wrapper">
          <span class="icon-menu icon"></span>
        </div>
        <div class="icon-wrapper">
          <span class="icon-progress icon"></span>
        </div>
        <div class="icon-wrapper">
          <span class="icon-bright icon"></span>
        </div>
        <div class="icon-wrapper">
          <span class="icon-a icon">A</span>
        </div>
      </div>
    </transition>
  </div>
</template>

<script>
import Epub from 'epubjs'

const DOWNLOAD_URL = '/2018_Book_AgileProcessesInSoftwareEngine.epub'
global.ePub = Epub
export default {
  data () {
    return {
      isMenuShow: false
    }
  },
  methods: {
    // 电子书解析和渲染
    showEpub () {
      // 生成Ebook
      this.book = new Epub(DOWNLOAD_URL)
      // 生成Rendition,通过Book.renderTo方法生成
      this.rendition = this.book.renderTo('read', {
        width: window.innerWidth,
        height: window.innerHeight
      })
      // 通过Rendition.display渲染电子书
      this.rendition.display()
    },
    prevPage () {
      if (this.rendition) {
        this.rendition.prev()
      }
    },
    nextPage () {
      if (this.rendition) {
        this.rendition.next()
      }
    },
    toggle () {
      this.isMenuShow = !this.isMenuShow
    }
  },
  mounted () {
    this.showEpub()
  }
}
</script>

<style lang="scss" scoped>
  @import "~styles/global";

  .ebook {
    position: relative;
    overflow-y: hidden;
    .title-wrapper {
      display: flex;
      justify-content: space-between;
      align-items: center;
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: px2rem(48);
      z-index: 101;
      background-color: #fff;
      box-shadow: 0 px2rem(8) px2rem(8) rgba(0, 0, 0, 0.15);
      &.slide-down-enter, &.slide-down-leave-to {
        transform: translate3d(0, -100%, 0);
      }
      &.slide-down-enter-to, &.slide-down-leave {
        transform: translate3d(0, 0, 0);
      }
      &.slide-down-enter-active, &.slide-down-leave-active {
        transition: all .3s linear;
      }
      .left {
        flex: 0 0 px2rem(60);
        @include center;
      }
      .right {
        flex: 1;
        display: flex;
        justify-content: flex-end;
        .icon-wrapper {
          flex: 0 0 px2rem(40);
          @include center;
        }
      }
    }
    .read-wrapper {
      .read {

      }
      .mask {
        display: flex;
        position: absolute;
        left: 0;
        top: 0;
        width: 100%;
        height: 100%;
        z-index: 100;
        .left {
          width: px2rem(100)
        }
        .right {
          width: px2rem(100)
        }
        .center {
          flex: 1;
        }
      }
    }
    .menu-wrapper {
      display: flex;
      position: absolute;
      z-index: 101;
      left: 0;
      bottom: 0;
      width: 100%;
      height: px2rem(48);
      box-shadow: 0 px2rem(-8) px2rem(8) rgba(0, 0, 0, 0.15);
      background-color: #fff;
      .icon-wrapper {
        flex: 1;
        @include center
      }
    }
  }
</style>

动画效果解析

      &.slide-down-enter, &.slide-down-leave-to {
        transform: translate3d(0, -100%, 0);
      }
      &.slide-down-enter-to, &.slide-down-leave {
        transform: translate3d(0, 0, 0);
      }
      &.slide-down-enter-active, &.slide-down-leave-active {
        transition: all .3s linear;
      }

transition包裹在需要展示动画的标签外部,但动画的样式和包裹的标签属于同一级,所以要使用&表示和标签同一级

slide-down-enter表示开始显示时

slide-down-leave-to表示完全隐藏时

slide-down-enter-to表示完全显示时

slide-down-leave表示开始隐藏时

slide-down-enter-active表示显示的全过程

slide-down-leave-active表示隐藏的全过程

2个动画的方式差不多可以简写,而且以后可能还会用到,所以写入global.scss中

.slide-down-enter, .slide-down-leave-to {
  transform: translate3d(0, -100%, 0);
}

.slide-up-enter, .slide-up-leave-to {
  transform: translate3d(0, 100%, 0);
}

.slide-down-enter-to, .slide-down-leave,.slide-up-enter-to,.slide-up-leave{
  transform: translate3d(0, 0, 0);
}

.slide-down-enter-active, .slide-down-leave-active, .slide-up-enter-active, .slide-up-leave-active {
  transition: all .3s linear;
}