整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

C# 控件封装方法及其思路- TAB标签页

C# 控件封装方法及其思路- TAB标签页

.先展现示例

TabPage


实际应用示例


尾部贴完整代码,若只需要实现功能,直接复制代码即可。

1.首先分析 页面需求,需要添加 标签 ,点击标签传递参数。委托思路。

第一步:分析标签结构

标签页面:展示图标。文本信息。关闭按钮。

public class TabHeader : FlowLayoutPanel

受限声明一个控件,继承 FlowLayoutPanel 继承此控件主要是关键代码系统控件已经都具有,不用自己造轮子,按照真正开发思路,还是可以继承 control控件自定义分割,重新封装。根据个人喜好。

标签页 制作:

 public class TabHeaderItem : WenControl

声明一个标签页 继承WenControl : 此控件是个人封装的基础控件,若要了解可以查看往期文章。或者开源搜索 WenSkin。可以找到相关介绍。

            private TabHeader owner;
            public TabHeaderItem(TabHeader owner, string path)
            {
                this.owner=owner;
                this.Path=path;
                this.Width=120;
                this.Height=30;

                WenButton button=new WenButton()
                {
                    Size=new Size(16, 16),
                    Image=Properties.Resources.close,
                    ImageSize=new Size(14, 14),
                    TextImageRelation=TextImageRelation.Overlay,
                    Location=new Point(this.Width - 16 - 4, 7),
                };
                button.Click +=Button_Click;

                this.Controls.Add(button);
                this.Paint +=FileItemControl_Paint;


                this.MouseLeave +=TabHeaderItem_MouseLeave;
                this.MouseEnter +=TabHeaderItem_MouseEnter;
            }

声明构造函数,部分细节后续代码注意讲解。

此处最主要是 尺寸信息 当然可以直接 size=new size();个人习惯。



声明一个关闭按钮。可以采用gdi画,也可以直接添加一个图片按钮。

          private void Button_Click(object sender, EventArgs e)
            {
                owner.Controls.Remove(this);
                this.Dispose();
            }

文中就直接调用往期封装好的按钮控件。若需要了解可以跳转往期文章查看。讲解了关于按钮 封装。

this.Paint +=FileItemControl_Paint;

画标签 的文字和 内容,可以直接重写 也可以使用委托画,个人喜好。

            private void FileItemControl_Paint(object sender, PaintEventArgs e)
            {
                var recImage=new Rectangle(4, 4, 22, 22);
                var recStr=new Rectangle(28, 4, this.Width - 28 - 4 - 22 - 2, 22);

                Graphics g=e.Graphics.SetGDIHigh();
                g.DrawImage(Properties.Resources.file, recImage);

                g.DrawString(FileText, this.Font, Brushes.White, recStr, ControlHelper.StringConters);

            }

this.MouseLeave +=TabHeaderItem_MouseLeave;

this.MouseEnter +=TabHeaderItem_MouseEnter;

鼠标事件处理

          public void ReBackColor()
            {
                if (owner.selectedItem==this)
                {
                    this.BackColor=Color.FromArgb(63, 63, 70);
                }
                else
                {
                    this.BackColor=Color.Transparent;
                }
            }

            private void TabHeaderItem_MouseEnter(object sender, EventArgs e)
            {
                this.BackColor=Color.FromArgb(62, 62, 64);
            }

            private void TabHeaderItem_MouseLeave(object sender, EventArgs e)
            {
                ReBackColor();
            }


主要是鼠标移动到指定位置改变颜色,离开后颜色会变化。直接修改背景颜色即可。

可以设置一个选中颜色

需要在主体中明一个选中选线。便于后续比较改变背景颜色。

        #region 私有属性

        private TabHeaderItem selectedItem;

        #endregion

若需要暴露在 全部 可以将私有属性改为公有。然后声明变化。

接下来就是 添加标签代码。

文中有一个需求就是最新 添加标签在最前端,并选中。

        public void Add(string path)
        {
            TabHeaderItem f=new TabHeaderItem(this, path);

            foreach (TabHeaderItem item in this.Controls)
            {
                if (path==item.Path)
                {
                    this.Controls.SetChildIndex(item, 0);
                    re(item);
                    return;
                }
            }

            f.Click +=(s, e)=>
            {
                re(f);
            };
            this.Controls.Add(f);
            re(f);
            this.Controls.SetChildIndex(f, 0);

            void re(TabHeaderItem item)
            {
                ItemChanged?.Invoke(this, new TabEventArgs(path));
                var ci=selectedItem;
                selectedItem=item;
                ci?.ReBackColor();
                item.ReBackColor();
            }
        }

若有其他需求可直接更改即可。

        #region 委托

        public delegate void TabEventHandler(object sender, TabEventArgs e);

        public class TabEventArgs : EventArgs
        {
            public TabEventArgs(string path)
            {
                Path=path;
            }

            public string Path { get; set; }
        }

        public event TabEventHandler ItemChanged;

        #endregion


至此,完整解决一个tab标签。

完整代码块

EMO

麻不烧的Github

配合着源码,用心看完这篇文章,你便领悟了封装的精髓,麻雀虽小,五脏俱全。

前记

业务代码之外的代码,我想称之为增值代码

什么意思?

作为一个程序员,你应该除了完成领导安排的任务,你还应该有一些自己的时间,用来“玩”一些比较有意思的事情。

当现有框架、库满足不了我们需求的时候,我们应该尝试去自己造一些工具。也正是这些你所实现的,成就了他人,造就了自己

不信,你且想一想,他人会关心你写的具体的业务逻辑代码吗?我想他们更关心的是,你写的插件,是如何使用的吧,以及方不方便他们借此完成他们自己业务代码。

再通俗一点,他们不会记住你,但是他们会记住你的Api,因而忆起你

还有很重要的一点,所有的技术,都是服务于业务的,否则,就是扯皮。

背景

入职新公司以来,一直忙于开发业务,过程中,多处用到了领导写的牛逼工具。说实话,内心由衷的佩服,简直就是解放生产力,放到古代,就是要被封神滴。

举个例子:

领导花了一段时间,研究出了一个自动表单生成器。之前手写一个表单配置页,加上表单验证,可能需要半天,甚至更久。

现在呢?所有的表单、样式及验证,都可以通过代码配置实现,二十分钟可能就完成了。

由此,我悟出了一个道理:

重复地做一件事,不如用心地做“一件事”。

我想,你肯定也想成为他人口中的那个男人,但整天活在自己的世界里,你可能一时并不知道该如何去做,这里我想告诉你:

成长的一个关键性因素,就是来自于模仿。

对的,你可以先尝试着去阅读下他人的代码,看看别人的实现方式,再者可以去github上溜一圈,优秀项目太多了,仿着写去呗。

我自己是一名从事了多年开发的web前端老程序员,目前辞职在做自己的web前端私人定制课程,今年年初我花了一个月整理了一份最适合2019年学习的web前端学习干货,各种框架都有整理,送给每一位前端小伙伴,想要获取的可以关注我的头条号并在后台私信我:前端,即可免费获取。

只要你想学,你就一定能学会,只不过是实现的方式好与坏而已,这些是需要后期不断完善的。

鉴于本篇文章快要跑题了,不再多述,进入正题...

正文

1.组件和插件的区别与联系

区别

  • 组件的使用频率往往大于插件
  • 组件的作用范围往往小于插件

联系

  • 插件可以封装组件,组件可以暴露数据给插件

这里不做过多阐述,有兴趣可以参考下劳卜大大的这篇文章,写的很通俗易懂。

2.实现插件的必备因素

基础

你需要清楚的知道vue的一些高阶知识点以及相关内容,比如

  • Vue.extend构造器
  • $mount手动挂载实例
  • mixin混合注入
  • 父子组件传参、跨级组件传参
  • 理解Vue构造函数及prototype原形对象
  • npm官网注册账号
  • webpack打包
  • ...

技巧

以下这个技巧是今天开发的时候悟出来的,目测很有用:

别着急开发,先想着如何在开发中使用你的插件

什么意思?顺着我的思路捋下去


因为我想实现一个全局toast插件,大概用法



 this.$toast('那个男人') // todo

光弹出文案不行,应该有一个控制弹出方向的变量




 this.$toast('那个男人',{ position:'topCenter' })

全局toast的状态应该有多种,比如常见的成功、错误、警告、普通...









 // 成功success // 错误error // 警告warning // 普通info this.$toast('那个男人',{ position:'topCenter', type:'success' })

应该有一个时间变量去控制多长时间自动消失toast






this.$toast('那个男人',{ position:'topCenter', type:'success', closeTime: 3 // 控制3秒后消失toast })

会不会存在一种业务场景,我们不需要自动消失toast







 this.$toast('那个男人',{ position:'topCenter', type:'success', closeTime: 3 // 控制3秒后消失toast autoClose: false })

如果我想在toast结束后,触发一些回调动作,比如删除成功toast后刷新列表页面










 this.$toast('那个男人',{ position:'topCenter', type:'success', closeTime: 3 // 控制3秒后消失toast autoClose: true, callback () { ... } })

toast的内容,可能会很长,因此应该有两个变量分别控制toast宽度和高度












 this.$toast('那个男人',{ position:'topCenter', type:'success', closeTime: 3 // 控制3秒后消失toast autoClose: true, callback () { ... }, width:300, height:80 })

至此,基础功能应该都涵盖了,这个时候你要去考虑一些内建的问题

  1. 这么多配置项,我作为一个使用者,都关心吗?或者说,都需要配置吗?
  2. 针对不同的状态(success/error/warning/info),肯定要用不同的颜色区分,以及使用不同的图标,他们之间有什么关系吗?

配置项多应该怎么解决-默认值

默认给个type呗,比如我的项目中默认的type是info,当我在使用的时候,没有传入type时,默认为info

因为大部分的toast场景都是短暂的停留在页面,所以autoClose设置为true

又因为大部分的toast文案比较短,所以我的默认toast长宽设置为300、80应该足够了

...

以上默认配置,都可以在使用的时候,传入参数覆盖默认参数

针对不同的状态,toast图标、颜色、标题之间有什么联系?

本地存一个map映射配置表,根据传入的type,我就可以准确的知道图标、颜色、标题应该是什么

总结几点:

  • 插件对外暴露的参数应保持最少原则,聚焦使用者关注点
  • 插件或组件的实现应该要基于使用场景考虑
  • 开发一款组件或者插件,应该保持软件工程领域的开放封闭原则
  • 一款好的插件或组件并不是一蹴而就的,往往需要后期使用过程中发现问题,加以完善
  • 组件或者插件的文档一定要完善,并不是每一个使用它的人,都关心它的内部实现,他们更关心的可能仅是如何快速上手

实现

上文提到过,组件可以暴露数据给插件,对于这句话

我的理解是,组件是静态的,只是对外暴露一些参数入口props。插件,让我们可以动态的往其中注入一些自定义参数。具体的实现,还是在组件当中完成。

于是乎:我写了一个静态组件,通过props定义上文提到的相关变量

先看下script部分

再来看下html部分

可以看到,内部实现其实很简单,无非就是通过外部传入的props,控制内部的展示细节而已

到这里静态组件基本已经完成了(css样式代码不在这里贴了)

注意:

  • props定义的时候,最好用对象的写法,作为一定的约束
  • 变量名字最好做到见名知意
  • class名的绑定可以充分利用vue提供的数组以及对象形式或者配合计算属性完成



静态组件怎么变成插件使用呢?

这里不再做过多阐述,vue封装插件的常用方法主要有以下四种,有疑惑的话,建议观阅vue开发插件,当然我觉得你应该还需要去了解下Vue.extend的用法,插件的实现离不开它哦。

看下关键部分:该文件也是我们后期webpack打包(build)的入口文件

该文件内容涉及到的知识点,也是开发一个vue插件最核心的内容。里面的每一行代码,都充满了杀机~

至此,关于插件实现部分基本已经全部完成。

3.如何将自己的插件上传到npm上去

这里的话,网上的教程有很多,我理解你只需要了解以下几行代码的作用,就足够了
























 // webpack.config.js module.exports={ entry: process.env.NODE_ENV==='development' ? './src/main.js' : './src/index.js', output: { path: path.resolve(__dirname, './dist'), publicPath: '/dist/', filename: 'build.js', libraryTarget: 'umd' }, ... // package.json { "name": "mbs-toast", "description": "a toast plugin base on Vue2", "version": "1.0.0", "author": "xxx ", "license": "MIT", "private": false, "main": "dist/build.js", "scripts": { "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", "build": "cross-env NODE_ENV=production webpack --progress --hide-modules", ...
  • 这里的entry入口文件配置的意思是,开发环境时,入口文件用main.js(方便开发调试使用),打包时入口文件为index.js
  • filename是打包生成文件的名字,这里是webpack-simple模版默认使用的就是build.js,没有特殊需求的话,不建议改动
  • libraryTarget属性可能大家都会比较陌生,因为一般如果只在项目中使用 webpack 不需要关注这两个属性,但是如果是开发类库、插件,那么这两个属性就是必须了解的,不清楚的可以参考详解webpack中ibraryTarget属性
  • package.json文件中的main字段,指定了该npm包引用的入口(记住一定要记得添加,并且文件名应与上面第二点提到的保持一致)

这里我用的模版是自己在官方webpack-simple模版的基础上做了一些定制化的,里面为了方便我平时开发,加入了scss、eslint,这样的话,后面就不用每次手动install了,有兴趣的可以看下README,定制一份属于自己的脚手架模板

在你了解了上述背景后,你只需要执行以下几步即可实现皆大欢喜

顺利的话,现在你已经可以在正式项目中,通过


npm install -S xxx 安装你的私有包了

最后在你的入口文件注册你的插件



import toastPlugin from 'xxx'Vue.use(toastPlugin) // 这里Vue.use的第二个参数,可以通过全局配置,做一些自定义配置,有需要的自行前往学习
到这里,所有的一切,已尘埃落定

你可以在代码中愉快的使用了









this.$toast('尘埃落定', { callback () { console.log('hello world') }, type: 'success', // position: 'topRight', autoClose: false})

最后

我在写这个插件之前,在Github上看到一个大神封装的插件。四个字描述下,叹为观止,有兴趣的一定要去看下,我相信爱学习的你,一定会收获满满。同时在开发该插件时,一些样式及动画,也做了相应的参考。

该插件的源码已经上传mbs-toast,方便大家参考。同时,上述提到的form表单生成器,我也尝试着自己实现了一遍,有兴趣的可以一起加入哦。所有的插件以及组件目前都汇总在麻不烧的Github里,文档和README正在不断完善中~

码字不易,且行且珍惜!

源自:https://juejin.im/post/5dc42069f265da4d3962a8e4

声明:文章著作权归作者所有,如有侵权,请联系小编删除。

小希这次带来了进阶版的Vue3 + Vite项目框架的封装搭建

本文主要的切入点有

搭建过程

多入口打包

一般情况下,项目开发只有一个入口,只需要配置一个入口,一个项目

但有时多个同业务、同类型的项目,有很多可以复用的业务,组件,工具类等,就可以放在同一个代码库里进行维护,不用新建多个代码库

每个项目都有自己独立的入口,可以独立打包并进行部署,低耦合,不会相互影响,同时还可以复用相同的组件,业务等,可以大大地提高开发效率和后期的维护

调整项目结构目录

如上图所示,有两个项目,分别是app1,app2,每个项目都有自己独有的main.ts入口文件,App.vue文件,以及路由,仓库pinia,组件等,同时也有共有的组件,utils工具类等

配置过程

在package.json中,将下图单入口的配置

改为

{
  "scripts": {
    "dev:app1": "vite serve src/app1/ --config ./vite.config.ts",
    "dev:app2": "vite serve src/app2/ --config ./vite.config.ts",
    "build:app1": "vue-tsc && vite build",
    "build:app2": "vue-tsc && vite build"
  },


这样配置可实现项目的独立运行,独立打包

在vite.config.ts中配置:

/* 项目名称 */
//采用这种方式可以动态获取项目名称,当然,如果项目少可以手动配置
let appName=process.env.npm_lifecycle_event
appName=appName.slice(appName.indexOf(':') + 1) //app1、app2

export default defineConfig({
  root: `./src/${appName}/`,
  build: {
    rollupOptions: {
      input: {
        [appName]: path.resolve(__dirname, `src/${appName}/index.html`)
      },
      output: {
        chunkFileNames: 'js/[name]-[hash].js',
        entryFileNames: 'js/[name]-[hash].js',
        assetFileNames: '[ext]/[name]-[hash].[ext]',
      }
    }
  }
})


测试项目的运行和打包构建

打包构建

pnpm build:app1
pnpm build:app2

自动化生成项目基础模版

基于多入口打包,也就是一个代码库同时维护多个同类型的项目情况下,可以通过配置实现自动化生成项目基础模板,这样,当需要在代码库新建一个新项目时,可以通过命令行快速创建

前置工具

inquirer

这个插件用来询问用户输入项目名称,这是一个比较在处理命令行交互比较常见的库

主要用于实现命令行交互式界面。帮助我们与用户进行交互式交流

它有几个特点:提供错误反馈,询问问题,解析输入,验证答案

详细可参考 命令行交互工具inquirer

安装

pnpm add  inquirer@^8.0.0 -S


配置过程

在package.json里添加

"scripts": {
    "init-app": "node ./src/utils/initApp/index.ts"
}


当执行这个命令时,会自动去执行,在本地utils文件夹下的initApp文件里的js脚本,在src目录下会自动生成一个新的文件夹(项目)

在utils下新增initApp文件夹以及index.ts和temlate

在index.ts添加以下代码

#!/usr/bin/env node
console.log('您正在创建项目')
const path=require('path')
const fs=require('fs')
const inquirer=require('inquirer')
const stat=fs.stat
const targetDir=path.resolve(__dirname, './template')
//复制文件目录
const copyFile=(targetDir, resultDir)=> {
  // 读取文件、目录
  fs.readdir(targetDir, function (err, paths) {
    if (err) {
      throw err
    }
    paths.forEach(function (p) {
      const target=path.join(targetDir, '/', p)
      const res=path.join(resultDir, '/', p)
      let read
      let write
      stat(target, function (err, statsDta) {
        if (err) {
          throw err
        }
        if (statsDta.isFile()) {
          read=fs.createReadStream(target)
          write=fs.createWriteStream(res)
          read.pipe(write)
        } else if (statsDta.isDirectory()) {
          fs.mkdir(res, function () {
            copyFile(target, res)
          })
        }
      })
    })
  })
}

const question=[
  {
    type: 'input',
    name: 'name',
    message: '请输入项目名称:'
  }
]

const createProject=()=> {
  // 询问用户问题
  inquirer
    .prompt(question)
    .then(({ name })=> {
      // name 为输入的项目名称
      name=name.trim()
      if (!name) {
        console.log('项目目录不能为空')
        // 如果输入空,继续询问
        createProject()
        return false
      }
      // 目标路径,要放在module目录下
      const resultDir=path.resolve(__dirname, '../../', name)
      // fs.access()方法用于测试文件是否存在
      fs.access(resultDir, function (err, data) {
        if (err) {
          // 创建文件
          fs.mkdir(resultDir, function (err, data) {
            if (err) {
              throw err
            }
            // 复制模版文件
            copyFile(targetDir, resultDir)
          })
          console.log(`${name} 项目已创建成功`)
        } else {
          console.log(`${name} 项目目录已存在,请输入其他名称`)
          // 不存在,继续询问
          createProject()
        }
      })
    })
    .catch((err)=> {
      console.log(err)
    })
}
createProject()


注:此代码copy==> Vue3项目框架搭建封装,一次学习,终身受益【万字长文,满满干货】

在temlate文件夹下新增项目所需要的文件目录,main.ts以及App.vue是必须的,因为它是独立的项目

使用

app3项目自动生成

持久化pinia仓库数据

数据存储在缓存(内存)中,优点读写更快,可以保存任意的js类型数据和对象,比如当我们刷新浏览器的时候,数据会丢失,所以需要实现pinia持久化

持久化插件pinia-plugin-persist

安装

pnpm add pinia-plugin-persist


import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persist'

const pinia=createPinia()
pinia.use(piniaPersist)

createApp({})
  .use(pinia)
  .mount('#app')


使用

// store/use-user-store.ts
import { defineStore } from 'pinia'

export const useUserStore=defineStore('storeUser', {
  state: ()=> {
    return {
      firstName: 'S',
      lastName: 'L',
      accessToken: 'xxxxxxxxxxxxx'
    }
  },
  actions: {
    setToken (value: string) {
      this.accessToken=value
    }
  },
  persist: {
    enabled: true,
    //这里可以单独给每个字段配置存储的形式sessionStorage/localStorage
    //paths配置state里的字段,不同的数据采取不同的存储方式
    strategies: [
      { storage: sessionStorage, paths: ['firstName', 'lastName'] },
      { storage: localStorage, paths: ['accessToken'] },
    ],

  }
})


strategies 字段说明:

属性

描述

key

自定义存储的 key,默认是 store.$id

storage

可以指定localStorage/sessionStorage,或者自定义存储类型,默认为 sessionStorage

paths

state 中的字段名,按组打包储存

也可以自定义存储类型,更多具体配置戳pinia-plugin-persist插件官网地址

源码解析

核心是通过 store.$subscribe去监听仓库数据,当仓库数据发生变化时会触发回调,更改本地缓存数据,当刷新后就会从本地缓存取出相关的数据

import { PiniaPluginContext } from 'pinia'


type Store=PiniaPluginContext['store']; //pinia插件上下文
type PartialState=Partial<Store['$state']>;

//调用函数将仓库数据存储到本地

export const updateStorage=(strategy: PersistStrategy, store: Store)=> {
  const storage=strategy.storage || sessionStorage //可以自定义存储类型,默认为sessionStorage
  const storeKey=strategy.key || store.$id   //可以自定义存储的 key,默认是 store.$id 

 //判断是否有配置paths,如果没有就缓存一整个仓库中的state
  if (strategy.paths) {
  
  //遍历paths里面的字段,并通过 store.$state[key]获取相应的数据
    const partialState=strategy.paths.reduce((finalObj, key)=> {
      finalObj[key]=store.$state[key]
      return finalObj
    }, {} as PartialState)
    
    //存储到本地
    storage.setItem(storeKey, JSON.stringify(partialState))
  } else {
    storage.setItem(storeKey, JSON.stringify(store.$state))
  }
}

export default ({ options, store }: PiniaPluginContext): void=> {
    //判断enabled是否为true
    
  if (options.persist?.enabled) {
    const defaultStrat: PersistStrategy[]=[{
      key: store.$id,
      storage: sessionStorage,
    }]

    const strategies=options.persist?.strategies?.length ? options.persist?.strategies : defaultStrat

    strategies.forEach((strategy)=> {
      const storage=strategy.storage || sessionStorage
      const storeKey=strategy.key || store.$id
      
     //根据key判断是否在本地缓存中,如果在刷新后会从本地缓存中将数据赋给pinia仓库的state
      const storageResult=storage.getItem(storeKey)

     // 如果本地中存在同步数据,更新仓库state数据
     //(比如浏览器刷新后会进行判断,如果有数据会赋值给pinia仓库的state,实现pinia持久化)
      if (storageResult) {
        store.$patch(JSON.parse(storageResult))
        updateStorage(strategy, store)
      }
    })
    
    //通过$subscribe监听state,仓库数据更改会触发回调同步更改本地数据
    store.$subscribe(()=> {
      strategies.forEach((strategy)=> {
        updateStorage(strategy, store)
      })
    })
  }
}


引入nprogess进度条

通过显示进度条的形式,来提高用户体验,可用在进入/离开路由时触发动画,也可在发接口时使用

安装

pnpm add nprogress -S


基本用法

只需调用start()和done()即可控制进度条。

NProgress.start();
NProgress.done();


使用场景

切换路由

router.beforeEach((to, from, next)=> {
  NProgress.start()
  next()
})
router.afterEach(()=> {
  NProgress.done()
})


发请求时

// axios请求拦截器
axios.interceptors.request.use(
config=> {
    NProgress.start() // 设置加载进度条(开始..)
    return config
},
error=> {
    return Promise.reject(error)
}
)
// axios响应拦截器
axios.interceptors.response.use(
function(response) {
    NProgress.done() // 设置加载进度条(结束..)
    return response
},
function(error) {
    return Promise.reject(error)
}
)


其它详细配置请戳官网:ricostacruz.com/nprogress/

viewport 适配方案 - postCSS插件

介绍

PostCSS 是一种 JavaScript 工具,可将你的 CSS 代码转换为抽象语法树 (AST),然后提供 API(应用程序编程接口)用于使用 JavaScript 插件对其进行分析和修改。

Autoprefixer主要功能是解析CSS并使用Can I Use中的值向CSS规则添加供应商前缀。以兼容各种浏览器,部分CSS属性需要加上不同的前缀以兼容不同的浏览器。通过配置Autoprefixer,自动为CSS属性添加对应浏览器的前缀。

postcss-px-to-viewport 用于将单位为 px 的尺寸转换为视口单位(vw, vh, vmin, vmax)

下面用到Autoprefixer和postcss-px-to-viewport这两个插件进行viewport适配

PostCSS 配置

安装

pnpm add postcss-px-to-viewport -D
pnpm add  autoprefixer -D


创建postcss.config.js并配置

// postcss.config.js
module.exports=()=> {
  return {
    plugins: {
      autoprefixer: {},
      'postcss-px-to-viewport': {
        unitToConvert: 'px', // 需要转换的单位,默认为"px"
        viewportWidth: 1920, // 设计稿的视口宽度
        unitPrecision: 5, // 单位转换后保留的精度
        propList: ['*'], // 能转化为vw的属性列表
        viewportUnit: 'vw', // 希望使用的视口单位
        fontViewportUnit: 'vw', // 字体使用的视口单位
        selectorBlackList: [], // 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。
        minPixelValue: 1, // 设置最小的转换数值,如果为1的话,只有大于1的值会被转换
        mediaQuery: false, // 媒体查询里的单位是否需要转换单位
        replace: true, //  是否直接更换属性值,而不添加备用属性
        exclude: undefined, // 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件
        include: undefined, // 如果设置了include,那将只有匹配到的文件才会被转换
        landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
        landscapeUnit: 'vw', // 横屏时使用的单位
        landscapeWidth: 1920 // 横屏时使用的视口宽度
      }
    }
  }
}


效果如下

不同视口宽度,界面会响应性变化


作者:小希学前端
链接:https://juejin.cn/post/7327965216826032154