uniapp中videojs、renderjs的使用

在uniapp中使用了某些前端库或iframe,需要操作这些库中的dom的时候, 而uni上又没有document等基础对象。也就无法操作这些dom去实现一些交互逻辑,那么,涉及到这些的前端类库就无法使用,例如html2、canvas、image、video。而要用这些怎么办,这是用就出现了renderjs这种视图层工具来进行渲染。大幅降低逻辑层和视图层的通讯损耗,提供高性能视图交互能力

使用方法:

  1. 在原有script下方再添加script,其中lang="renderjs"固定, module="demo",module的名字任意起
  2. 可以通过 this.$ownerInstance 获取当前组件的 ComponentDescriptor 实例。类似于vm
  3. 视图层绑定事件通过 module名称 . 逻辑层定义方法,有两个参数,1.事件对象event,2. 当前组件实例ComponentDescriptor
  4. 两个script间的通信需要通过:this.$ownerInstance 全局组件实例 或者 事件参数ComponentDescriptor 身上的callMethod方法,去抛出方法、传值,类似于vue组件间emit
  5. 可以使用 vue 组件的生命周期不可以使用 App、Page 的生命周期

使用参考:https://blog.csdn.net/dabaooooq/article/details/129272111

代码

uniapp在APP端video层级最高,不能被其它覆盖,引入videojs实现,使用renderjs实现原生的DOM操作。

javascript 复制代码
<template>
    <div class="full_screen_video" v-show="visible" :class="{ 'normal': !fullScreen }">
        <!-- 遮盖video 添加点击事件 -->
        <div class="video_mask"  @click="foldShortVideo" ></div>
        <p v-if="fullScreen" class="back" @click="closeDialogHandle">
            <image src="@i/common/back_white.svg" alt=""></image>
            <span>{{ title}}</span>
        </p>
        <view 
            :info="videoItem" 
            :change:info="videos.updateVideo" 
            :options="videoOptions" 
            :change:options="videos.optionsChange"
            class="video_box"
            ref="videoEle" 
            id="videoEle">
        </view>
        <view class="video_control_box" v-if="fullScreen" >
            <!-- 开始结束时间 -->
            <view class="video_time">
                <span>{{ formatTime(videoCurrentTime) }} </span> <span>{{ formatTime(videoTotalTime) }}</span>
            </view>
            <!-- 视频播放器 -->
            <div class="video_control">
                <p class="disp_ac">
                    <image  v-if="!isStart" src="@i/video/icon_video_start.svg" mode="widthFix" @tap="startAndStop('start')" alt=""></image>
                    <image v-else src="@i/video/icon_video_stop.svg" mode="widthFix" @tap="startAndStop('stop')" alt=""></image>
                    <image  src="@i/video/icon_video_next.svg" mode="widthFix" @tap="playNext" alt="" v-if="videoList.length > 0"></image>
                </p>
                <p>
                    <view :class="{'normal_mode':normalMode}"  class="formation_select_box mr60" @click="showOption = !showOption">
                        <span class="select_text">{{ speedValue +'x'}}</span>
                        <view class="select_menu" :class="{ ani: showOption }" v-show="showOption">
                            <view class="select_menu_item" v-for="item in speedOptions" :key="item.value">
                                <span :class="{'active': speedValue === item.value}" @click="changeSpeed(item.value)">{{ item.label +'x'}}</span>
                            </view>
                        </view>
                    </view>
                    <image v-if="normalMode" src="@i/video/full_screen.svg" mode="widthFix" @tap="fullScreenVideo" alt=""></image>
                </p>
            </div>
        </view>
        <view v-else class="video_control_box video_control_box_small" >
            <div class="time_bar_box">
                <div class="time_btn">
                    <image  v-if="!isStart" src="@i/video/icon_video_start.svg" mode="widthFix" @tap="startAndStop('start')" alt=""></image>
                    <image v-else src="@i/video/icon_video_stop.svg" mode="widthFix" @tap="startAndStop('stop')" alt=""></image>
                    <image  src="@i/video/icon_video_next.svg" mode="widthFix" @tap="playNext" alt="" v-if="videoList.length > 0"></image>
                </div>
                <div class="time_bar">
                    <span>{{ formatTime(videoCurrentTime) }} </span> <span>{{ formatTime(videoTotalTime) }}</span>
                </div>
            </div>
            <div class="video_btn">
                <view  class="formation_select_box mr50" @click="showOption = !showOption">
                    <span class="select_text">{{ speedValue +'x'}}</span>
                    <view class="select_menu" :class="{ ani: showOption }" v-show="showOption">
                        <view class="select_menu_item" v-for="item in speedOptions" :key="item.value">
                            <span :class="{'active': speedValue === item.value}" @click="changeSpeed(item.value)">{{ item.label +'x'}}</span>
                        </view>
                    </view>
                </view> 
                <image src="@i/video/download.svg" mode="widthFix" @tap="downloadVideo" alt=""></image>
                <image v-if="normalMode" src="@i/video/full_screen.png" mode="widthFix" @tap="fullScreenVideo" alt=""></image>
            </div>
        </view>
        <!-- 视频列表 -->
        <div class="video_list" v-if="showVideoList && videoList.length" >
             <scroll-view scroll-x="true" >
                <div class="video_list_item">
                    <div v-for="(item, index) in videoList" :key="index" class="video_item" :class="{'active': index === currentVideoIndex}" @click="playVideo(item, index)">
                        <div class="video_img">
                            <image class="snapshot" :src="item.snapshot && !item.imgError ? item.snapshot : defaultSnapshot" @error="item.imgError = true" alt="snapshot" ></image>
                            <span class="total_time" v-if="item.end && item.start">
                                {{formatTime(item.end - item.start)}}
                            </span>
                        </div>
                        <div class="clip_info">
                            <div class="text_over">{{item.name || item.label  || ''}}</div>
                            <div class="video_list_time">{{ formatDate(item.createTime).split('-').slice(0,3).join('/').split(' ').slice(0,1).join() }}</div>
                        </div>
                    </div>
                </div>
            </scroll-view>
        </div>
    </div>
</template>

<script>
export default {
    name: 'pad-video-play',  // 在主视频内播放的组件,短片会跳转到主视频对应的时间
    props: {
        // 控制显示隐藏
        visible: {
            type: Boolean,
            default: false
        },
        // 主视频下的短视频播放列表
        videoList: {
            type: Array,
            default:() => []
        },
        // 主视频播放源
        videoMain: {
            type: Object,
             default: () => { }
        },
        // 视频类型, 默认为全屏播放
        videoType: {
            type: String,
            default: 'full_screen'
        },
    },
    data() {
        return {
            videoCurrentTime: 0, // 视频当前时间
            videoTotalTime: 0,   // 视频总时长
            isStart: false,      // 是否开始
            volumeValue: 1,     // 音量初始值(0-1)
            showVideoList: true,    // 是否显示视频列表
            currentVideoIndex: -1,   // 当前播放的视频下标
            defaultSnapshot: require("static/images/video/default_snapshot.svg"), // 默认缩略图
            videoOptions: {
                closeDialog: false
            },   // 视频操作选项
            fullScreen:  this.videoType === 'full_screen',    // 是否是全屏
            speedValue: '1.0',     // 初始播放速度
            showOption:false, // 控制倍速弹窗
            speedOptions: [     // 阵型选项
                { label: '2.0', labelEn: '2.0', value: '2.0' },
                { label: '1.5', labelEn: '1.5', value: '1.5' },
                { label: '1.25', labelEn: '1.25', value: '1.25' },
                { label: '1.0', labelEn: '1.0', value: '1.0' },
                { label: '0.5', labelEn: '0.5', value: '0.5' },
            ],
            videoItem: {},
            title:''
        };
    },
    computed: {
        // 是否是正常模式下的视频播放
        normalMode() {
            let boolean = this.videoType === 'normal';
            return boolean;
        }
    },
    watch: {
        currentVideoIndex(newVal) {
            this.$emit('change-video-index', newVal);
        },
        videoMain: {
            handler (newVal) { 
                this.videoItem = {
                    url: newVal.url
                }
                this.title = newVal.name 
            },
            immediate: true,
            deep:true
        },
        visible(newVal) {
            if (newVal) { 
                 this.videoOptions = {
                    closeDialog: false,
                    volumeChangeOption: { volume: 1 } 
                }
                this.volumeValue = 1;
            }
        }
    },

    methods: {
        foldShortVideo(){
            this.showVideoList = !this.showVideoList
        },
        // 关闭弹框
        closeDialogHandle () {
            this.videoOptions = {
                closeDialog: true
            }
            if (this.videoType === 'full_screen') {
                // setTimeout(() => { 
                    this.$emit('cancelVideo', false);
                // },100)
            } else {
                this.fullScreen = false;
            }
            this.showVideoList = false;
            this.$emit('update:visible',false)
        },
        // 开始视频
        onPlayerPlay() {
            this.speedValue = '1.0'
            this.isStart = true;
        },
        // 实时更新
        onTimeUpdate(currentTime){
            this.videoCurrentTime = currentTime;
        },
        // 暂停视频
        onPlayerPause() {
            this.isStart = false;
        },
        // 加载视频源数据
        onLoadedmetadata(totalTime) {
            this.videoTotalTime = totalTime;
        },
        // 开始或暂停视频
        startAndStop(type) {
            this.videoOptions = {
                startAndStopOption: { type }
            }
        },
        // 改变视频音量
        volumeChange(volume) {
            this.volumeValue = volume;
            this.videoOptions = {
                volumeChangeOption: { volume } 
            }
        },
        // 播放视频
        playVideo(item, index) {
            this.speedValue = '1.0'
            // // 在正常模式播放下, 不可重复点击当前视频
            if (this.normalMode && item && this.currentVideoIndex === index) {
                return;
            }
            this.$set(this.videoItem, 'startTime', item.start);
            this.currentVideoIndex = index;
            this.videoList.forEach(item => {
                item.forceUpdate = false
            })
            item.forceUpdate = true
            
        },
        // 改变视频的速度
        changeSpeed(speed) {
            this.speedValue = speed || '1.0';
            this.videoOptions = {
                playbackRate: { speed }
            }
        },
        // 播放下一个
        playNext(){
            this.speedValue = '1.0'
            this.videoList.forEach(item => {
                item.forceUpdate = false
            })
            let index = this.currentVideoIndex;
            
            if(this.currentVideoIndex+1<this.videoList.length){ 
                this.currentVideoIndex = index+1;
                this.$set(this.videoList[index+1],'forceUpdate',true)
                this.$set(this.videoList, index + 1, this.videoList[index + 1]);
                this.$set(this.videoItem, 'startTime', this.videoList[index+1].start);
            }else{ 
                this.currentVideoIndex = 0;
                this.$set(this.videoList[0],'forceUpdate',true)
                this.$set(this.videoList, 0, this.videoList[0]);
                this.$set(this.videoItem, 'startTime', this.videoList[0].start);
            }
        
        },
        // 进入/退出 全屏视频
        fullScreenVideo() {
            this.fullScreen = !this.fullScreen;
        },
        // 格式化时间
        formatTime(result) {
            let h =  Math.floor(result / 3600) < 10 ? "0" + Math.floor(result / 3600) : Math.floor(result / 3600);
            let m = Math.floor((result / 60) % 60) < 10 ? "0" + Math.floor((result / 60) % 60) : Math.floor((result / 60) % 60);
            let s = Math.floor(result % 60) < 10 ? "0" + Math.floor(result % 60) : Math.floor(result % 60);
            return Math.floor(result / 3600) == 0 ? m + ":" + s : h + ":" + m + ":" + s;
        },
        // 格式化日期
        formatDate(createTime){
            let date = new Date(createTime);
            let str = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
            return str;
        }
    }
};
</script>

<script module="videos" lang="renderjs">
    import './video-offset.js';         // videojs 引入
    import Videojs from 'video.js';
    import 'video.js/dist/video-js.css';
    export default {
        data() {
            return {
                videoPlayer: null, // 当前视频播放器
            }
        },
        mounted() {
           
        },
        methods: {
          
            // 初始化视频
            initVideoJS(item) {
              	if(this.videoPlayer){
					this.videoPlayer.dispose();
					this.videoPlayer = null;
				}
                let that = this;
                let videoEle = document.createElement('video');
                videoEle.style = 'width:100%; height:100%';
                videoEle.setAttribute("class", "video-js vjs-big-play-centered");
                let videos = document.getElementById('videoEle');
                console.log(videos);
                videos.appendChild(videoEle)
       
                let option = {
                    controls: true,     // 是否显示控制条
                    preload: 'auto',    // 是否预加载视频数据
                    muted: false,       // 是否静音
                    language: 'zh-CN',  // 设置语言
                    autoplay: true,    // 自动播放, 正常模式下不进行自动播放
                    sources: [  // 视频源
                        {
                            type: "video/mp4",
                            src: item.url
                        }, {
                            type: "video/webm",// webm格式
                            src: item.url
                        }, {
                            type: "video/mov", // mov格式
                            src: item.url
                        },
                    ],
                    controlBar: {       // 设置控制条组件
                        children: [
                            { name: 'progressControl' },     // 播放进度条
                        //     // { name: 'fullscreenToggle' }     // 全屏按钮
                        ]
                    }
                };
                // video.js初始化实例化的对象
                this.videoPlayer = Videojs(videoEle, option, function onPlayerReady() {
                    // 开始视频
                    this.on('play', function() {
                        that.$ownerInstance.callMethod('onPlayerPlay');
                    })
                    // 暂停视频
                    this.on('pause', function() {
                        that.$ownerInstance.callMethod('onPlayerPause');
                    })
                    // 实时更新
                    this.on('timeupdate', function() {
                        let currentTime = that.videoPlayer ? that.videoPlayer.currentTime() : 0;
                        that.$ownerInstance.callMethod('onTimeUpdate', currentTime);
                    })
                    // 加载视频源数据
                    this.on('loadedmetadata', function() {
                        let totalTime = parseInt(that.videoPlayer.duration()) || 0;
                        that.$ownerInstance.callMethod('onLoadedmetadata', totalTime);
                    })
                });
            },
            // 监听 videoData 数据变更
            updateVideo(newValue) {
                if(!newValue || !newValue.url) return;
                if(this.videoPlayer && newValue.startTime){
                    this.videoPlayer.currentTime(newValue.startTime)
                    return
				}
                this.initVideoJS(newValue);
            },
            // 监听改变视频操作选项
            optionsChange(newValue) {
                if (newValue && newValue.startAndStopOption) {
                    // 开始或暂停视频
                    let { type } = newValue.startAndStopOption;
                    type === 'start' ? this.videoPlayer.play() : this.videoPlayer.pause();
                } else if (newValue && newValue.volumeChangeOption) {
                    // 改变视频音量
                    let  { volume }  = newValue.volumeChangeOption;
                    this.videoPlayer && this.videoPlayer.volume(volume);
                } else if (newValue && newValue.closeDialog) {
                    console.log('销毁了')
                    // 销毁videoJs实例
                    this.videoPlayer && this.videoPlayer.dispose();
                    this.videoPlayer = null
                } else if (newValue && newValue.continuePlay) {
                    // 继续播放视频
                    let { url } = newValue.continuePlay;
                    this.initVideoJS(url);
                } else if(newValue &&  newValue.playbackRate){
                    let { speed } = newValue.playbackRate;
                    // 改变视频的播放速度
                    this.videoPlayer && this.videoPlayer.playbackRate(speed);
                }
            }
        }
    }
</script>

<style lang='less' scoped>
.video_mask {
    position: absolute;
    width: 100%;
    height: calc(100% - 62.81rpx);
    top: 0;
    left: 0;
    background-color: transparent;
    z-index: 1;
}
::v-deep {
    // 视频style
    .video_box{
        width: 100%;
        height: 100%;
        video{
            object-fit:fill;
        }
    }
    .vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar{
        opacity: 1;
    }
    .video-js{
        position: relative;
        .vjs-control-bar {
            width:663.32rpx;
            left: 43.34rpx;
            bottom: 47.9rpx;
            background-color: transparent;
            z-index: 105;
            opacity: 1;
        }
        .vjs-play-progress{
            background: #38CB89;
            &::before{
                color: #38CB89;
            }
        }
        .vjs-slider{
            background-color: rgba(255, 255, 255, 0.36);
        }
    }
}
.disp_ac{
    display: flex;
    align-items: center;
}
/* 文字超出部分设置为... */
.text_over {
    max-width: 85%;
    word-break: break-all;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2; /* 这里是超出几行省略 */
    overflow: hidden;
}
.full_screen_video{
    width: 100%;
    height: 100%;
    position: fixed;
    z-index: 102;
    left: 0;
    top: 0;
    box-sizing: border-box;
    color: #fff;
    font-family: 'SysFontR';
    // 默认视频样式
    &.normal{
        width: 100%;
        height: 100%;
        position: relative;
        .video_list,.video_shrink{
            display: none;
        }
        .video_control_box{
            width: 100%;
            height: 37.69rpx;
            position: absolute;
            left: 0;
            bottom: 4.71rpx;
            z-index: 5;
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 0 10.05rpx;
            box-sizing: border-box;
            &.video_control_box_small{
                .formation_select_box{
                    bottom: 7rpx;
                    .select_text{
                        font-size: 10.05rpx;
                    }
                    .select_menu{
                        bottom:0;
                        transform:scale(.8)                    
                    }
                }
            }
            .time_bar_box{
                width: 100%;
                height: 12.25rpx;
                display: flex;
                align-items: center;
                .time_btn{
                    image{
                        width: 11.93rpx;
                        height: 9.42rpx;
                        margin-right: 10.05rpx;
                    }
                }
                .time_bar{
                    flex: 1;
                    display: flex;
                    justify-content:space-between;
                    font-size: 9.74rpx;
                    color: #f3f3f3;
                }
            }
            .video_btn{
                width: 100%;
                display: flex;
                justify-content: flex-end;
                text-align: right;
                margin-top: 6.28rpx;
                image{
                    width: 10.99rpx;
                    height: 10.05rpx;
                    margin-right: 15.7rpx;
                }
            }
        }
        .video_control{
            height: 47.11rpx;
            bottom: 4.71rpx;
            image{
                width: 10.05rpx !important;
                height: 10.05rpx !important;
                margin-right: 15.7rpx;
            }
        }
        ::v-deep {
            .vjs-control-bar{
                width: calc(100% - 74.75rpx - 37.69rpx);
                left: 76.75rpx;
                bottom: 27.84rpx;
            }
        }
    }
    .back{
        position: absolute;
        z-index: 200;
        top: 0;
        left: 0;
        width: 100%;
        height: 58.59rpx;
        padding-left: 14.64rpx;
        font-size: 13.82rpx;
        display: flex;
        align-items: center;
        cursor: pointer;
        background: linear-gradient(180deg, #000000 0%, rgba(0, 0, 0, 0) 100%);
		image{
			width: 6.44rpx;
			height: 12.3rpx;
			margin-right: 20.1rpx;
		}
    }
    .formation_select_box{
        position: relative;
        border-radius: 3.77rpx;
        display: flex;
        align-items: center;
        justify-content: center;
        &.normal_mode{
            right: 118rpx;
        }
        .select_text{
            font-family: 'AkrobatMedium';
            font-size: 11.31rpx;
            color: #fff;
            position: relative;
        }
        .select_menu {
            width: 43.66rpx;
            position: absolute;
            bottom: 18.04rpx;
            left: -13rpx;
            z-index: 999;
            padding: 13.82rpx 0 0;
            text-align: center;
            box-sizing: border-box;
            border-radius: 3.77rpx;
            background: #000000;
            color: #F3F3F3;
            .select_menu_item {
                position: relative;
                z-index: 2;
                margin-bottom: 12.56rpx;
                >span {
                    display: inline-block;
                    width: 100%;
                    height: 100%;
                    box-sizing: border-box;
                    font-size: 10.05rpx;
                     font-family: AkrobatRegular;
                    &.active{
                        color: #38CB89;
                    }
                }
            }
        }
        .ani {
            animation: ani 0.2s;
        }
        @keyframes ani {
            0% {
                transform: scaleY(0);
            }
            100% {
                transform: scaleY(1);
            }
        }
        .mask_box {
            width: 70vw;
            height: 100vh;
            position: fixed;
            left: 0;
            top: 60rpx;
            z-index: 99;
            background: transparent;
        }
    }
    .video_time{
        position: absolute;
        bottom: 49.94rpx;
        width: 100%;
        box-sizing: border-box;
        padding:0 15.7rpx;
        line-height: 14.13rpx;
        display: flex;
        justify-content:space-between;
        z-index: 104;
    }
    .video_control{
        position: absolute;
        left: 0;
        z-index: 5;
        bottom: 5.85rpx;
        width: 161.13rpx;
        height: 35.15rpx;
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 0 19.04rpx;
        box-sizing: border-box;
        width: 100%;
        p{
            display:flex;
            align-items: center;
            image{
                width: 17.59rpx;
                height: 17.59rpx;
                margin-right: 15.23rpx;
                // opacity: .6;
            }
            .video_volume{
                position: relative;
                .video_volume_slider{
                    display: none;
                    position: absolute;
                    right: 30px;
                    top: -85px;
                    padding-top: 5px;
                    background-color: rgba(0, 0, 0, 0.36);
                    &.hover{
                        display: inline-block;
                    }
                }
            }
        }
    }
    .video_list{
        position: absolute;
        width: 100%;
        top: 0;
        right: 0;
        z-index: 201;
        background: rgba(29, 29, 29, 0.8);
        backdrop-filter: blur(50px);
        color: #fff;
        padding-top: 25.13rpx;
        padding-bottom: 6.28rpx;
        box-sizing: border-box;
        display: flex;
        // transition: all 200ms;
        .close_img{
            position: absolute;
            top: 50%;
            transform: translateY(-50%);
            right: 236.81rpx;
			width: 20.5rpx;
			height: 75rpx;
        }
        .video_item{
            width: 83.54rpx;
            flex-shrink: 0;
            display: flex;
            flex-direction: column;
            margin-left: 12.56rpx;
            box-sizing: border-box;
            display: flex;
            &.active{
                    .text_over{
                        font-family: SysFontM;
                        color: #38CB89;
                    }
            }
            .video_img{
                width: 100%;
                height: 47.11rpx;
                box-sizing: border-box;
                position: relative;
                image.snapshot{
                    border-radius: 3.77rpx;
                    width: 100%;
                    height: 100%;
                    
                }
                .total_time{
                    font-family: SysFontR;
                    height: 12.89rpx;
                    position: absolute;
                    left: 2.92rpx;
                    bottom: 2.92rpx;
                    padding: 0 5.85rpx;
                    font-size: 8.2rpx;
                    line-height: 12.89rpx;
                    color: rgb(255, 255, 255);
                    background: rgba(30, 30, 30, .6);
                }
            }
            .clip_info{
                width: 100%;
                box-sizing: border-box;
                font-size: 8.2rpx;
                color: rgba(255, 255, 255, .8);
                position: relative;
                padding: 4.71rpx 0;
                // .clip_label{
                //     font-size: 10.68rpx;
                //     color: rgba(255, 255, 255,1);
                //     line-height: 14.06rpx;
                //     margin-bottom: 7.54rpx;
                // 	display: flex;
                // 	align-items: center;
                // }
            }
        }
    }
    .video_list_item {
        display: flex;
        width: 100%;
        overflow-x: auto;
    }
    .video_shrink{
        position: fixed;
        top: 50%;
        transform: translateY(-50%);
        right: 0;
		width: 20.5rpx;
		height: 75rpx;
    }

    .video_list_time {
        font-family: AkrobatRegular;
        margin-top: 3px;
    }
}
</style>;
相关推荐
悦涵仙子1 小时前
CSS中的变量应用——:root,Sass变量,JavaScript中使用Sass变量
javascript·css·sass
兔老大的胡萝卜1 小时前
ppk谈JavaScript,悟透JavaScript,精通CSS高级Web,JavaScript DOM编程艺术,高性能JavaScript pdf
前端·javascript
cs_dn_Jie4 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic5 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿5 小时前
webWorker基本用法
前端·javascript·vue.js
清灵xmf6 小时前
TypeScript 类型进阶指南
javascript·typescript·泛型·t·infer
小白学大数据6 小时前
JavaScript重定向对网络爬虫的影响及处理
开发语言·javascript·数据库·爬虫
qq_390161776 小时前
防抖函数--应用场景及示例
前端·javascript
334554327 小时前
element动态表头合并表格
开发语言·javascript·ecmascript
John.liu_Test7 小时前
js下载excel示例demo
前端·javascript·excel