Bootstrap

前端问题记录(持续更新...)

此文档记录在项目中遇到一些问题的解决方法,如果你有更好的解决方法,我们一起讨论啊

在这里插入图片描述

目录

1、Vue在子组件向父组件传值的方法中增加自定义参数

// Editor 组件
<Editor
  :getContent="ruleForm.content"
  @getEditorCon="getEditorCon($event, 1)"
/>
  // getEditorCon获取子组件传递过来的值
  // $event是传递的值
  // 1 是自定义的参数

2、利用图片的url获取图片的宽高

      // 创建一个图片对象
      var imgObj = new Image()
      // 利用onload方法拿到宽高,onload方法在赋值src之前是为了避免拿宽高失败
      imgObj.onload = function () {
        console.log(imgObj.width)
        console.log(imgObj.height)
      }
      // 将图片的src赋值为我们要拿到宽高的图片链接(http:// 一定要加)
      imgObj.src = 'http://studiestest-1257270075.cos.ap-beijing.myqcloud.com/1584424866.png'

3、更新node-sass报错

node版本和包版本不匹配,需要更新,但是执行npm rebuild node-sass会报错,这个时候先执行:
cnpm i -D node-sass,再执行npm rebuild node-sass

4、scss @mixin and @include

@mixin定义css变量,@include引入css变量,可以实现css的复用

/* 定义 */
$yellow-color: #FE5722;
$y-textcolor: #FFFFFF;
@mixin button-yellow {
  background-color: $yellow-color;
  color: $y-textcolor;
}
/* 引用 */
.info_btn {
  @include button-yellow;
}
/* 实际渲染 */
.info_btn {
  background-color: #FE5722;
  color: #FFFFFF;
}

书写形式有很多,也可以在@mixin的css中@include其他的css,再将其@include其他地方,官方代码(scss官方地址):

/* 定义 */
@mixin reset-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

@mixin horizontal-list {
  @include reset-list;

  li {
    display: inline-block;
    margin: {
      left: -2px;
      right: 2em;
    }
  }
}
/* 引用 */
nav ul {
  @include horizontal-list;
}
/* 实际渲染 */
nav ul {
  margin: 0;
  padding: 0;
  list-style: none;
}
nav ul li {
  display: inline-block;
  margin-left: -2px;
  margin-right: 2em;
}

@mixin@include也支持传参数(官方代码):

/* 定义 */
@mixin rtl($property, $ltr-value, $rtl-value) {
  #{$property}: $ltr-value;

  [dir=rtl] & {
    #{$property}: $rtl-value;
  }
}
/* 引用 */
.sidebar {
  @include rtl(float, left, right);
}
/* 实际渲染 */
.sidebar {
  float: left;
}
[dir=rtl] .sidebar {
  float: right;
}
/* 定义 */
@mixin replace-text($image, $x: 50%, $y: 50%) {
  text-indent: -99999em;
  overflow: hidden;
  text-align: left;

  background: {
    image: $image;
    repeat: no-repeat;
    position: $x $y;
  }
}
/* 引用*/
.mail-icon {
  @include replace-text(url("/images/mail.svg"), 0);
}
/* 实际渲染 */
.mail-icon {
  text-indent: -99999em;
  overflow: hidden;
  text-align: left;
  background-image: url("/images/mail.svg");
  background-repeat: no-repeat;
  background-position: 0 50%;
}

5、pre 标签 vs white-space样式属性

pre 标签可以识别文本中的空格和换行,并将空格和换行展示在页面上,但是pre标签的文字样式是默认样式,只有修改该标签的文字样式才可修改,不继承父标签的文字样式,而且pre标签超出盒子之后是不会换行的,如果需要超出盒子自动换行,需要为其设置相应样式

white-space CSS 属性是用来设置如何处理元素中的 空白
pre-wrap这个值,既可以保留文本中所有的空白,超出盒子还会自动换行,所以相对于pre标签,white-space: pre-wrap这个css样式更加方便。

6、vue 无感刷新

  • App.vue 文件中:
export default {
  name: 'App',
  provide() {
    return {
      reload: this.reload
    }
  },
  data() {
    return {
      isRouterAlive: true
    }
  },
  methods: {
    reload() {
      this.isRouterAlive = false
      this.$nextTick(function() {
        this.isRouterAlive = true
      })
    }
  }
}
  • 需要无感刷新的页面中:
export default {
  inject: ['reload'],
  data() {
    return {
    }
  },
  methods: {
    // 刷新页面
    refresh() {
      this.reload()
    }
  }
}

7、兼容苹果浏览器的时间显示

前端写代码渲染时间格式基本上都是2020-02-02 20:20:20,但是在苹果浏览器上看就会显示成了NaN-NaN-NaN NaN:NaN:NaN,这是因为苹果浏览器不兼容这种时间格式,而苹果系统的时间显示格式为2020/02/02 20:20:20,我们只需要在展示的时候用正则转换一下就可以了:

'2020-02-02 20:20:20'.replace(/-/g, '/')
// 2020/02/02 20:20:20

展示在html中:

<span>{{ `${scope.row.create_time}`.replace(/-/g, '/') }}</span>

如果每个渲染时间的地方都要使用一下正则,会很繁琐,后期维护也会增加难度,我们可以写成指令,针对需求全局和局部都可以

  filters: {
    formatData(value) {
      return value.replace(/-/g, '/')
    }
  }

8、滚动条样式的修改

当页面在浏览器中打开的时候,浏览器自身的滚动条样式不好看,可以根据需求自己设置滚动条的样式,如果是页面的侧边栏,不管滚动条的样式是什么样的,看着都会有点多余,直接隐藏是最好

/* 修改滚动条的样式: 写在有侧边栏的div上,例如ul */
&::-webkit-scrollbar-track:vertical {
  background-color: #E9E9E9;
}
&::-webkit-scrollbar-thumb {
  background-color: #C1C1C1;
  border-radius: 4px;
}
&::-webkit-scrollbar{
  width: 7px;
  height: 7px;
}

/* 隐藏滚动条的css */
&::-webkit-scrollbar { width: 0 !important }
-ms-overflow-style: none;
overflow: -moz-scrollbars-none;
scrollbar-width: none;

9、vue数据变化页面不刷新的问题

原因:由于JavaScript的限制,Vue不能检测数组和对象的变化,例如:当你利用索引直接设置数组选项时;当你修改数组的长度时;当你给响应式对象里面加一个原本没有的值时。

向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。
出现最多的情况就是编辑页面,拿到详情数据渲染在页面上(响应式数据可能只有一个空对象,获取详情数据赋值给空对象),这时候更改表单(这时候更改的是原本data里面没有,后来我们赋值的数据),我们会发现,页面毫无改变(是因为我们获取到详情的数据赋值给data里面的值,即使这样,这个详情的数据也不是响应式的,所以即使数据改变,页面是不会发生改变的),但是当我们换一个文本框或者下拉框改变值的时候,刚才改变的内容才更新到页面上,查找原因发现,其实当我们编辑文本框或者其他表单元素的时候,值已经发生改变,只是没有更新视图,vue有个方法Vue.set,可以帮我们将新增的这些字段变为响应数据(触发视图更新)

  • this.ruleForm 要改变的对象,codeSum 要改变的对象的具体值, item.code_sum 要改变的值
    如果是改变数组,第二个参数为数组的索引
  • this.$set其实是向响应式对象中添加一个property,既然是响应的了,所以数据改变,页面也会改变了
  • 如果我们不想用set,我们也可以在data中声明所要用到的变量,这样初始值就是响应的,后期赋值也会是响应的
// Vue.set的使用
this.$set(this.ruleForm, 'codeSum', item.code_sum)
// 上面代码可以理解为:
this.ruleForm.codeSum = item.code_sum

10、小程序点击事件的传参方式

<view bindtap="passQuery" data-index="1">点击事件传参</view>
passQuery: function(e){
   // 传递的参数
   let query = e.currentTarget.dataset['index'];
}

11、vue的passive修饰符

百度好多文章去理解passive修饰符,只有这篇看懂了,忍不住给截图了,哈哈哈
当然,怎么少了原文链接 传送门
在这里插入图片描述

12、解决浏览器自动填充账号和密码的问题

当我们在电脑端登录一个网页,登录成功之后浏览器会询问是否要记住账号和密码,如果我们选择记住,下次登录的时候,账号和密码就会为我们自动填充到的输入框中。浏览器有一个填充机制,如果有密码框,且密码框前面有一个文本框,就会认为是登录的表单,被填充进去账号和密码,但是注册的时候也会有这种表单形式,但是注册不应该填充数据进去,这时候如果我们在form表单的最前面增加两个不可见的输入框,一个文本框,一个密码框,那么浏览器就会将账户和密码填充到这两个不可见的文本框内

<form id="registerForm" >
	<input style="opacity: 0;position:absolute;width:0;height:0;">
	<input type="password" style="opacity: 0;position:absolute;width:0;height:0;">
	
	......
</form>

13、vue跳转页面默认定位到顶部

vue项目中,跳转页面其实是路由的切换,并没有刷新页面,就会发现一个问题,浏览过的页面会保持上次浏览的位置,在main.js中添加如下代码就会实现默认定位在顶部:

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import ElementUI from "element-ui";
import "element-ui/lib/theme-chalk/index.css";

Vue.use(ElementUI);
Vue.config.productionTip = false;

// 切换路由默认显示在页面顶部 ↓
router.beforeEach((to, from, next) => {
  // chrome
  document.body.scrollTop = 0
  // firefox
  document.documentElement.scrollTop = 0
  // safari
  window.pageYOffset = 0
  next()
});
// 切换路由默认显示在页面顶部 ↑

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount("#app");

14、自定义文本选中的颜色和背景色

字体选中默认字体颜色白色,背景色蓝色,想要与众不同,也是可以的~
为字体加上以下样式就OK了

  ::selection {
    color: #fff;
    background: #088;
  }

15、vue中在样式设置scoped的情况下修改element-ui默认样式

vue项目的style标签设置了scoped之后,再修改element-ui的默认样式,发现没有生效,这时候我们可以在element-ui的class前面加上/deep/或者>>>就可以了

注意/deep/>>>一定要写到element-ui的class前面,否则是不会生效的

.join-three {
  /* /deep/写在element-ui的class名字前面,使用>>>同理 */
  /deep/.el-table {
    tr {
      cursor: pointer;
    }
    td,
    th.is-leaf {
      border: none;
      height: 63px;
    }
    td {
      padding: 0 20px;
      color: #333;
      font-size: 16px;
    }
    th.is-leaf {
      background-color: #eee;
      color: #333;
      font-size: 18px;
      font-weight: bold;
      padding: 0 20px;
      cursor: default;
    }
  }
}

16、vue点击跳转到当前路由报错

vue项目中,我们点击导航栏跳转路由,当我们点击跳转的路由刚好是当前路由的时候,控制台会报错Avoided redundant navigation to current location: "/login".

  • 解决办法:在router.js中增加如下代码
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function push(location) {
  return originalPush.call(this, location).catch(err => err);
};

17、ES6的数组去重

let arr = [1, 2, 3, 5, 5, 4, 4, 4, 6, 8, 5, 1, 2, 3, 6, 5, 3, 2];
arr = [...new Set(arr)];
console.log(arr); // [1, 2, 3, 5, 4, 6, 8]

18、js判断数组和对象的类型

Object.prototype.toString.call([]) // '[object Array]'
Object.prototype.toString.call({}) // '[object Object]'

19、小程序组件无法使用app.wxss中的样式

在组件的js文件中加上addGlobalClass即可

Component({
  options: {
    addGlobalClass: true
  }
})

20、网页中嵌入自动播放的视频

    <div class="top_video">
      <video src="../../assets/images/e0c9f85faada395ffd1ca9f918cc4a24.mp4" muted="muted" autoplay="autoplay" loop="loop" class="mainVideo"></video>
    </div>
.top_video .mainVideo {
    display: block;
    position: relative;
    top: 0;
    width: 100%;
    z-index: 100;
}

21、css启用事件穿透

点击图片想要图片下面的盒子触发点击事件,就给图片加上以下样式

pointer-events: none;

22、小程序去除button组件边框

页面中的自定义分享按钮,必须使用小程序的原生组件button,为其添加open-type='share'属性,但是页面中的分享按钮只有一个icon图片,需要修改button的默认样式
注意:buttonborder属性的修改需要修改after上的,否则不生效

  button{
    display: inline-block;
    padding: 0;
    vertical-align: middle;
    background-color: transparent;
  }
  button::after {
      border: none;
  }

23、微信小程序wxs根据点赞数,省略展示(超出万)

function numerConver (num) {
  var numStr = num.toString();  
  // 一万以内直接返回  
  if (numStr.length < 5) {    
    return numStr;
  } else if (numStr.length > 4) {
    // 万
    var decimal = numStr.substring(numStr.length - 4, numStr.length - 4 + 1);    
    return parseFloat(parseInt(num / 10000) + '.' + decimal) + 'w';
  }
}

24、微信小程序阻止事件冒泡

小程序的点击事件一般用bindtap,如果嵌套多个盒子都有点击事件,就会触发事件冒泡,将bindtap事件换成catchtap即可禁止事件冒泡

25、数组对象去重

let arr = [
	{
		name: '1111',
		age: 12
	},
	{
		name: '1211',
		age: 12
	},
	{
		name: '1111',
		age: 12
	}
]
let stemp = {};
arr = arr.reduce((item, next) => {
    stemp[next.name] ? '' : stemp[next.name] = true && item.push(next);
    return item
}, [])
console.log(arr)
  • 结果:
    在这里插入图片描述

26、清除input选中的文件

// uploads 是 input
document.getElementById("uploads").reset();

27、将连接转为二维码显示在页面中

vue 有一个组件qrcode可以直接将连接地址转为二维码,html也可以使用,只需要把QRCode.js下载到本地即可,用法和资源地址:我在这~
下面是vue的用法(需要先npm install --save qrcode):

import QRCode from 'qrcode';

// path是连接地址,url是转的二维码图片
QRCode.toDataURL(path).then(url => {
   this.EWMUrl = url
   this.EWMDialog = true
 }).catch(err => {
   console.error(err)
 })

28、vue路由中hash与history的区别

之前写项目就知道两个模式是带#号和不带#的区别,对于深层区别不是很了解,但是这么常用的东西还是要理解其原因:
在这里插入图片描述

29、MD5加密

blueimp-md5 插件可以直接生成加密的MD5,支持中文数字字符还有表情

npm下载:
npm install blueimp-md5

<script src="js/md5.min.js"></script>
var hash = md5('value') // "2063c1608d6e0baf80249c42e2be5804"

30、控制台打印数组中有值,但是length却是0

在控制台当你展开的时候才获取地址数据也就是说当你打印的东西是异步的,你在异步之前打印,控制台点击展开会获取最后的结果打印length为0证明打印的时候数组就是没有数据

31、CSDN插入图片使图片左对齐

CSDN 插入图片默认是居中的,是因为你在插入图片的时候图片链接后面默认拼接了一个#pic_center,去掉这个图片就会左对齐了,加上这个就是居中
在这里插入图片描述

32、去除input的type值为number时候的加减按钮

     /* 谷歌:*/
      input::-webkit-outer-spin-button,
      input::-webkit-inner-spin-button{
          -webkit-appearance: none !important;
      }
      /* 火狐 */
      input[type="number"]{
    	-moz-appearance: textfield;
	  }  

33、什么是语法糖

语法糖其实就是对语言的功能并没有影响,但是更方便程序员使用,通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

例如:vuev-model就是value值和input事件的合并。

	<input v-model="something" />
	<input v-bind:value="something" v-on:input="something = $event.target.value" />

第一行的代码其实只是第二行的语法糖。然后第二行代码还能简写成这样:

	<input :value="something" @input="something = $event.target.value" />

34、阻止浏览器的默认行为:

	//阻止浏览器默认行为触发的通用方法 
	function stopDefault(e){ 
	    //防止浏览器默认行为(W3C) 
	    if(e && e.preventDefault){ 
	        e.preventDefault(); 
	    } 
	    //IE中组织浏览器行为 
	    else{ 
	        window.event.returnValue=fale;
	        return false;
	    } 
	} 

35、前端加密插件

AES加密:

RSA加密:

36、微信小程序修改Vant Weapp组件的样式

在控制台Wxml中找不到这些元素,拿不到class样式,但是在选中页面上的元素时候,会有小弹窗显示当前标签的class,直接修改小弹窗显示的class样式即可

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

37、微信小程序自定义swiper指示点样式

样式修改代码在下面,还有一个,把轮播图的指示点放在轮播图下面,有个投机取巧的办法,把swiper的高度设高一点,swiper-item的高度设低一点,这样指示点就会在轮播图的下面啦

/* 默认指示点的样式 */
.swiper .wx-swiper-dot {
  width: 15rpx;
  height: 15rpx;
  background: white;
  border-radius: 15rpx;
}
 
/* 选中指示点的样式 */
.swiper .wx-swiper-dot.wx-swiper-dot-active {
    width: 30rpx;
    height: 15rpx;
    background: white;
    border-radius: 15rpx;
}

38、微信小程序保存图片到本地

老板刚提出这个需求的时候,我傻啦吧唧的还去自己写方法,微信有属性可以直接办到。。。
在这里插入图片描述
image标签加上这个属性,就可以识别图片
在这里插入图片描述
保存图片,收藏转发,一键搞定

39、微信小程序scroll-view在苹果6,8手机上划不动

按照以下布局和样式写就可以了
在这里插入图片描述
在这里插入图片描述

40、微信小程序右上角点出来的分享按钮是灰色的?

如果你当前页面的js文件中没有onShareAppMessage方法,那个分享按钮就会是灰色的,想要可以分享,在其js文件中添加onShareAppMessage方法,返回值是个对象,分享标题,分享图片,还有分享参数,都可以自定义

  // 自定义分享
  onShareAppMessage() {    
    return {
      title: '测试标题',
      imageUrl: 'http://img...',
      path: `/pages/index/index?id=1`
    }
  }

当然,也可以进行全局定义,只需要将如下代码放在app.js中,但是单个页面中有onShareAppMessage方法的话,会以单页面里的配置为准

!function(){
  var PageTmp = Page;
 
  Page = function (pageConfig) {
     
    // 设置全局默认分享
    pageConfig = Object.assign({
      onShareAppMessage:function () {
        return {
          title:'分享标题',
          path: '默认分享路由+id',
          imageUrl: '分享图片地址',
        };
      }
    }, pageConfig);
 
    PageTmp(pageConfig);
  };
}();

41、官网视频背景代码

<div class="video-banner">
  <video
    muted
    class="video-my"
    loop="loop"
    autoplay="autoplay"
    src="./images/video/05.mp4"
    type="video/mp4"
    poster=""
  >抱歉,您的浏览器不支持内嵌视频</video>
</div>

42、深拷贝和浅拷贝的区别

  • 深拷贝:拷贝所有的属性,并且地址也与原来的不同,这样的话,你改变当前的属性也不会影响原来的
  • 浅拷贝:就是直接赋值的这种,地址相同,当你改变现在的值,原来的值也会跟着改变

深拷贝的实现:
a:JSON.parse(JSON.stringify(obj)) => 这种方式只能对属性不是function的有用
b:Object.assgin({},target) => 这种方式第一层是深拷贝,第二层是浅拷贝
c:jquery的JQ.extend
d:采用递归来进行拷贝
e:采用扩展运算符var a = {…obj};

43、微信map组件回到自己位置

这个坐标大家都不陌生,点击一下,地图上就会锁定到我们所在的位置,如何自定义这个按钮呢?
在这里插入图片描述
将下面方法绑定到按钮的点击事件上,map组件上id和ref都设置为map

  clickcontrol(e) {
	let mpCtx = wx.createMapContext("map");
	mpCtx.moveToLocation();
  },

44、使用接口上传文件,file对象总是为空

let file = e.target.files[0];
var dataF = new FormData();
dataF.append("imgUpload", file);

45、导出文件

接口封装带上responseType: 'blob'

export function warehousesExport (data = {}) {
  return request({
    url: `/warehouses/check/export`,
    method: 'get',
    data,
    responseType: 'blob'
  })
}

请求拦截中判断一下,如果是二进制的话,返回整个response

request.interceptors.response.use((response) => {
  // 如果返回值是二进制,return 整个 response
  if (response.config.responseType === 'blob') return response
}

页面中的请求:

async exportData() {
  const response = await warehousesExport(that.searchForm)
  if (response.data) {
	  const fileName = response.headers['content-disposition'].replace('attachment;filename=', '')
	  const url = window.URL.createObjectURL(new Blob([response.data]))
	  const link = document.createElement('a')
	  link.style.display = 'none'
	  link.href = url
	  link.setAttribute('download', decodeURI(fileName))
	  document.body.appendChild(link)
	  link.click()
  }
}

46、微信小程序返回上个页面携带参数

返回页面是不能携带参数,我们想要改变上个页面的值,除了用本地缓存之外还有一个方法,看代码(注意uniapp的写法和微信小程不一样):

 //获取当前页面js里面的pages里的所有信息。
 let pages = getCurrentPages();
 // 减1 是当前页面,减2是上个页面
 let prevPage = pages[pages.length - 2];
 // 修改参数:微信小程序的写法
 prevPage.setData({
   页面A的参数: e.currentTarget.dataset.current,
 })
 // 修改参数:uniapp的写法
 prevPage.$vm.页面A的参数 = e.currentTarget.dataset.current;
 // 返回上一级页面。
 wx.navigateBack({
   delta: 1
 })

47、微信小程序获取顶部安全区域高度

在自定义小程序顶部导航的时候,需要计算顶部高度,使我们自定义的和小程序自带的高度相等

// 获取顶部安全区域
let that = this
wx.getSystemInfo({
	success(a) {
		let b = wx.getMenuButtonBoundingClientRect()
		let px = (b.height + a.statusBarHeight) + ((b.top - a.statusBarHeight) * 2)
		let rpx = px * (750 / a.windowWidth)
		that.statusBarHeight = a.statusBarHeight * (750 / a.windowWidth)
		// that.barHeight 就是顶部安全高度,单位(rpx)
		that.barHeight = rpx
	}
})

48、rem.js

window.onload = function() {
  /*720代表设计师给的设计稿的宽度,你的设计稿是多少,就写多少;100代表换算比例,这里写100是
    为了以后好算,比如,你测量的一个宽度是100px,就可以写为1rem,以及1px=0.01rem等等*/
  getRem(720, 100);
};
window.onresize = function() {
  getRem(720, 100);
};
function getRem(pwidth, prem) {
  var html = document.getElementsByTagName("html")[0];
  var oWidth =
    document.body.clientWidth || document.documentElement.clientWidth;
  html.style.fontSize = (oWidth / pwidth) * prem + "px";
}

49、忽略打包发生的eslint校验

babel.config.js中的’plugins’: [‘transform-remove-console’]

50、注册全局方法

1、在utils文件夹中创建function.js文件,文件中写一些全局的方法
2、在main.js中引入function.js

import function from './utils/function'

3、注册到vue实例中

Vue.prototype.$function = function

4、使用

// downloadXlsx 是function.js中得方法
this.$function.downloadXlsx(res)

51、添加和删除路由中的参数

  • 新增
const query = Object.assign({}, this.$route.query, { status: newVal })
this.$router.replace({ query })
  • 删除(全部删除)
this.$router.replace({})

52、苹果手机兼容border-radius属性

  border-radius: 16rpx;
  overflow: hidden;
  /* 下面是浏览器 */
  /* -webkit-appearance:none; */
  /* 下面是小程序 */
  -webkit-backface-visibility: hidden;
  -webkit-transform: translate3d(0, 0, 0);

53、微信小程序动态更新数组

在这里插入图片描述

54、直接获取对象的key和value,返回数组在这里插入图片描述

55、便利数组对象,判断是否有属性为空

let imgList = [
	{
		id: 1,
		url: ''
	},
	{
		id: 2,
		url: 'http://11111.png'
	}
]
// 如果imgList数组中某个对象的url为空,就会return false
if (imgList.some((val) => val.url == '' )) {
  showToast("图片正在上传,请稍后...")
  return false
}

56、微信小程序在不使用scroll标签的情况下返回顶部

 wx.pageScrollTo({
   scrollTop: 0,
   duration: 300
 })

57、微信小程序rpx和px换算

rpx换算px:屏幕宽度/750
px换算成rpx:750/屏幕宽度

58、css水平垂直居中

  display: flex;
  align-items: center;
  justify-content: center;
;