| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- <template>
- <view class="x-skeleton" :style="variableStr">
- <!-- 骨架屏 -->
- <view v-if="skeletonLoading" class="x-skeleton__wrapper" :class="[ startFadeOut && 'fade-out' ]"
- :style="{ padding: skeletonConfigs.padding, background: background }">
- <view v-for="(row, rowIndex) in gridRowsArr" :key="rowIndex" class="x-skeleton__wrapper__rows"
- :style="{ marginBottom: rowIndex < gridRowsArr.length - 1 ? skeletonConfigs.gridRowsGap : 0 }">
- <view v-for="(column, columnIndex) in gridColumnsArr" :key="columnIndex" class="x-skeleton__wrapper__columns"
- :style="{
- flexDirection: skeletonConfigs.itemDirection,
- alignItems: skeletonConfigs.itemAlign,
- marginRight: columnIndex < gridColumnsArr.length - 1 ? skeletonConfigs.gridColumnsGap : 0,
- }">
- <view v-if="skeletonConfigs.headShow" class="x-skeleton__wrapper__head" :class="[ animate && 'animate' ]"
- :style="{
- width: skeletonConfigs.headWidth,
- height: skeletonConfigs.headHeight,
- borderRadius: skeletonConfigs.headBorderRadius,
- marginRight: (skeletonConfigs.itemDirection == 'row' && skeletonConfigs.textShow) ? skeletonConfigs.itemGap : 0,
- marginBottom: (skeletonConfigs.itemDirection == 'column' && skeletonConfigs.textShow) ? skeletonConfigs.itemGap : 0
- }"></view>
- <view v-if="skeletonConfigs.textShow" class="x-skeleton__wrapper__text" :style="skeletonConfigs.textRowStyle">
- <view v-for="(text, textIndex) in textRowsArr" :key="textIndex" class="x-skeleton__wrapper__text__row"
- :class="[animate && 'animate']" :style="{
- width: text.width,
- height: text.height,
- flex: skeletonConfigs.textItmesFlex,
- borderRadius: skeletonConfigs.textBorderRadius,
- marginLeft: textIndex > 0 ? skeletonConfigs.textItmesDivide : 0,
- marginBottom: textIndex < textRowsArr.length - 1 ? skeletonConfigs.textRowsGap : 0
- }"></view>
- </view>
- </view>
- </view>
- </view>
- <!-- 插槽 -->
- <view v-else>
- <slot></slot>
- </view>
- </view>
- </template>
- <script>
- import XSkeletonConfigs from './x-skeleton-configs.js'
- export default {
- name: "x-skeleton",
- mixins: [XSkeletonConfigs],
- props: {
- // 骨架屏类型
- type: {
- type: String,
- default: '' //banner轮播图、info个人信息、text段落、menu菜单、list列表、waterfall瀑布流
- },
- // 是否展示骨架组件
- loading: {
- type: Boolean,
- default: true
- },
- // 是否开启动画效果
- animate: {
- type: Boolean,
- default: true
- },
- // 动画效果持续时间,单位秒
- animateTime: {
- type: [Number, String],
- default: 1.8
- },
- // 是否开启淡出动画
- fadeOut: {
- type: Boolean,
- default: true
- },
- // 淡出效果持续时间,单位秒
- fadeOutTime: {
- type: [Number, String],
- default: 0.3
- },
- // 骨架的背景色
- bgColor: {
- type: String,
- default: '#EAEDF5'
- },
- background: {
- type: String,
- default: '#FFFFFF'
- },
- // 骨架的动画高亮背景色
- highlightBgColor: {
- type: String,
- default: '#F9FAFF'
- },
- // 自定义配置
- configs: {
- type: Object,
- default: () => {
- return {
- // padding: '30rpx', //内边距
- // gridRows: 3, //行数
- // gridColumns: 2, //列数
- // gridRowsGap: '40rpx', //行间隔
- // gridColumnsGap: '24rpx', //竖间距
- // itemDirection: 'column', //head与text之间的排列方向(row、column)
- // itemGap: '16rpx', //head与text之间的间隔
- // itemAlign: 'center', //head与text之间的纵轴对齐方式(center、flex-start、flex-end、baseline)
- // headShow: true, //head是否展示
- // headWidth: '100%', //head宽度,支持百分比
- // headHeight: '400rpx', //head高度
- // headBorderRadius: '12rpx', //head圆角,支持百分比
- // textShow: true, //文本是否展示
- // textRows: 3, //文本的行数
- // textRowsGap: '12rpx', //文本间距
- // textWidth: ['40%', '85%', '60%'], //文本的宽度,可以为百分比,数值,带单位字符串等,可通过数组传入指定每个段落行的宽度
- // textHeight: ['30rpx', '20rpx', '20rpx'], //文本的高度,可以为数值,带单位字符串等,可通过数组传入指定每个段落行的高度
- // textBorderRadius: '6rpx', //文本的圆角,支持百分比
- }
- }
- }
- },
- computed: {
- gridRowsArr() {
- return new Array(Number(this.skeletonConfigs?.gridRows || []));
- },
- gridColumnsArr() {
- return new Array(Number(this.skeletonConfigs?.gridColumns || []));
- },
- textRowsArr() {
- if (!this.skeletonConfigs?.textShow) return [];
- if (/%$/.test(this.skeletonConfigs.textHeight)) {
- console.error('x-skeleton: textHeight参数不支持百分比单位');
- }
- const rows = []
- for (let i = 0; i < this.skeletonConfigs.textRows; i++) {
- const {
- gridRows,
- textWidth,
- textHeight
- } = this.skeletonConfigs;
- let item = {},
- // 需要预防超出数组边界的情况
- rowWidth = this.isArray(textWidth) ? (textWidth[i] || (i === gridRows - 1 ? '70%' : '100%')) : i ===
- gridRows - 1 ? '70%' : textWidth,
- rowHeight = this.isArray(textHeight) ? (textHeight[i] || '30rpx') : textHeight
- // 非百分比的宽度时,调整像素单位
- if (/%$/.test(rowWidth)) {
- item.width = rowWidth;
- } else {
- item.width = this.addUnit(rowWidth)
- }
- item.height = this.addUnit(rowHeight)
- rows.push(item)
- }
- return rows
- },
- variableStr() {
- let keys = ['animateTime', 'fadeOutTime', 'bgColor', 'highlightBgColor'];
- let str = keys.map(item => {
- if (item.indexOf('Time') > -1) {
- return `--${item}:${this[item]}s`
- } else {
- return `--${item}:${this[item]}`
- }
- }).join(";");
- return str;
- }
- },
- watch: {
- loading: {
- immediate: true,
- handler(value) {
- if (value) {
- this.skeletonLoading = true;
- } else {
- if (this.fadeOut) {
- this.startFadeOut = true;
- setTimeout(() => {
- this.skeletonLoading = false;
- this.startFadeOut = false;
- }, this.fadeOutTime * 1000);
- } else {
- this.skeletonLoading = false;
- }
- }
- }
- },
- type: {
- immediate: true,
- handler(value) {
- if (value === 'banner') {
- this.skeletonConfigs = this.bannerConfigs();
- } else if (value === 'info') {
- this.skeletonConfigs = this.infoConfigs();
- } else if (value === 'text') {
- this.skeletonConfigs = this.textConfigs();
- } else if (value === 'menu') {
- this.skeletonConfigs = this.menuConfigs();
- } else if (value === 'list') {
- this.skeletonConfigs = this.listConfigs();
- } else if (value === 'waterfall') {
- this.skeletonConfigs = this.waterfallConfigs();
- } else {
- this.skeletonConfigs = this.configs || {};
- }
- }
- }
- },
- data() {
- return {
- skeletonConfigs: this.configs || {},
- skeletonLoading: this.loading,
- startFadeOut: false,
- width: 0
- };
- },
- mounted() {
- },
- methods: {
- /**
- * @description 是否为数组
- * @param {object} value 需要判断的对象
- */
- isArray(value) {
- if (typeof Array.isArray === 'function') {
- return Array.isArray(value)
- }
- return Object.prototype.toString.call(value) === '[object Array]'
- },
- /**
- * @description 添加单位,如果有rpx,upx,%,px等单位结尾或者值为auto,直接返回,否则加上px单位结尾
- * @param {string|number} value 需要添加单位的值
- * @param {string} unit 添加的单位名 比如px
- */
- addUnit(value = 'auto', unit = 'px') {
- value = String(value);
- // 用uView内置验证规则中的number判断是否为数值
- return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value) ? `${value}${unit}` : value;
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- @mixin background {
- background: linear-gradient(90deg, var(--bgColor) 25%, var(--highlightBgColor) 37%, var(--bgColor) 50%);
- background-size: 400% 100%;
- }
- .x-skeleton {
- width: 100%;
- box-sizing: border-box;
- .x-skeleton__wrapper {
- display: flex;
- flex-direction: column;
- &__rows {
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- &__columns {
- display: flex;
- align-items: center;
- flex: 1;
- }
- &__head {
- width: 100%;
- @include background;
- }
- &__text {
- flex: 1;
- width: 100%;
- &__row {
- @include background;
- }
- }
- }
- .fade-out {
- opacity: 0;
- animation: fadeOutAnim var(--fadeOutTime);
- }
- @keyframes fadeOutAnim {
- from {
- opacity: 1;
- }
- to {
- opacity: 0;
- }
- }
- .animate {
- animation: skeletonAnim var(--animateTime) ease infinite;
- }
- @keyframes skeletonAnim {
- 0% {
- background-position: 100% 50%;
- }
- 100% {
- background-position: 0 50%;
- }
- }
- }
- </style>
|