Greasy Fork

Greasy Fork is available in English.

手机端浏览器功能扩展(原yandex视频横屏插件)

手机端可装插件浏览器(如yandex,kiwi)添加额外的功能。例如:双击视频全屏,双击快速搜索,左右滑动视频进度,单手手势操作等。(手势如:↓↑回到顶部,↑↓回到底部,→←后退,←→前进,→↓关闭标签页,→↑重新打开页面等)

当前为 2020-01-19 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name           手机端浏览器功能扩展(原yandex视频横屏插件)
// @name:en        Add additional functions to mobile browser
// @description    手机端可装插件浏览器(如yandex,kiwi)添加额外的功能。例如:双击视频全屏,双击快速搜索,左右滑动视频进度,单手手势操作等。(手势如:↓↑回到顶部,↑↓回到底部,→←后退,←→前进,→↓关闭标签页,→↑重新打开页面等)
// @description:en Add additional functions to mobile browser(like yandex browser).For example, double click to make video full screen , double click to search for selected text , slide to adjust video progress and sliding gesture by one hand.
// @version        3.7.5
// @author         L.Xavier
// @namespace      http://greasyfork.icu/zh-CN/users/128493
// @include        *
// @grant          GM_setValue
// @grant          GM_getValue
// @grant          GM_addStyle
// @grant          unsafeWindow
// @grant          window.close
// @grant          GM_openInTab
// @grant          GM_addValueChangeListener
// @run-at         document-start
// @note           功能说明:1.全屏下强制为横屏观看,支持重力感应切换方向。   2.添加双击全屏功能。一些网站全屏按钮无法使视频进入全屏模式(例如m.bilibili.com),在视频播放状态下,双击全屏(支持双击退出全屏)。   3.视频拖动进度功能,左右滑调整视频播放进度。  4.添加手势操作,方便单手操作浏览器。现有手势,↓↑回到顶部,↑↓回到底部,→←后退,←→前进,←↓刷新页面,←↑新建空白页,→↓关闭标签页,→↑重新打开页面。在手势设置界面可自行添加修改手势功能。(↑→↓←打开手势设置界面)    5.新增双击功能。双击页面,若有选中文字则百度搜索该关键字,若选中文字为网址,则打开网址页面。
// @v1.1.0        2019-01-01 - 1.添加竖屏模式。有些视频更适宜在竖屏状态下观看,全屏状态下,手机竖直摆放时会重新回到竖屏模式。2.添加双击全屏功能。一些网站全屏按钮无法使视频进入全屏模式(例如m.bilibili.com),新增在视频播放状态下,双击全屏(支持双击退出全屏)。该功能需要关闭yandex浏览器的"忽略网站的禁止字体缩放请求"功能,不关闭双击会缩放网页。
// @v1.1.1        2019-01-02 - 优化了代码逻辑
// @v1.1.2        2019-01-12 - Yandex浏览器版本18.11.1(安卓自2018年12月26日,版本号979),切换竖屏会退出全屏状态,暂时取消竖屏模式,其他保持不变。不是该版本浏览器,有竖屏观看需要的可以安装本脚本历史版本1.1.1。
// @v1.2.1        2019-02-01 - 重写了一部分代码,使之使用更加人性化。重新添加竖屏观看模式。使用方法,摇一摇切换竖屏观看,再摇一摇可换回横屏观看(可调节灵敏度,SHAKE_THRESHOLD参数,越小越灵敏)。火狐浏览器(安卓)使用该版本,除了双击全屏,其他功能正常。
// @v1.2.2        2019-02-03 - 防止误操作,摇一摇调整为全屏下执行。
// @v1.2.3        2019-02-04 - 修复了摇一摇短时间内可能触发两次的问题。
// @v1.2.4        2019-02-09 - 修复了后加载视频,某些功能可能需要多次点击才能生效的问题。
// @v1.2.5        2019-02-13 - 调整视频标签查找逻辑和事件方法,添加了更新说明。
// @v2.0.0        2019-08-22 - 新增视频滑动进行前进后退功能;修复在已有视频标签下,重新请求视频源双击全屏无效问题。
// @v2.1.0        2019-08-24 - 完善视频滑动前进后退进度,添加进度时间小tip(可能不同手机会有位置不居中的问题,大家可以用bilibili的桌面版页面测试)。优化代码逻辑。
// @v3.0.0        2019-10-02 - 横屏播放调整为桌面版启动,旧版yandex无横屏的小伙伴可以更新版本或用本脚本旧版本。因显示优先级问题,去除视频时间tip。新添加手势功能,有爱的小伙伴可自行玩耍,添加新手势。现有手势,→↑回到顶部,→↓回到底部,→←后退,←→前进,←视频进度后退,→视频进度前进。
// @v3.1.0        2019-10-03 - 调整视频横屏播放逻辑,修复脚本与浏览器竖屏播放功能冲突问题。去除摇一摇竖屏功能。
// @v3.1.1        2019-10-03 - 调整手势灵敏度
// @v3.1.2        2019-10-03 - 修复语法错误导致的横屏逻辑失效问题
// @v3.2.0        2019-10-06 - 精简优化代码。调整视频滑动进度函数,使进度随滑动距离成指数增加,减小在短距离时的时间进度。调整手势功能代码逻辑,下个版本将添加手势功能设置UI,可自己添加修改手势操作。
// @v3.2.1        2019-10-15 - 修复因代码冲突导致的视频标签查找问题,调整查找视频标签逻辑。调整视频滑动进度判断逻辑。
// @v3.3.0        2019-11-05 - 添加手势设置UI界面(略粗糙,见谅),↑→↓←打开手势设置界面。添加并调整一部分功能代码。
// @v3.3.1        2019-11-09 - 修复未引用tampermonkey的功能模块而导致的功能无法使用,修改ajax生成视频的查找逻辑。
// @v3.3.2        2019-11-11 - 修复可能因视频自动播放而导致video标签获取不到的问题。
// @v3.3.3        2019-11-30 - 微调手势设置UI样式,使某些错版页样式一致。微调视频滑动进度快慢。
// @v3.4.0        2020-01-03 - 手势灵敏度更灵敏(减小滑动距离判断)。修复Ajax重新请求后无法找到当前播放视频问题。调整视频滑动进度函数。
// @v3.4.1        2020-01-03 - 因灵敏度太高容易造成误滑,故重新调整判断距离。
// @v3.5.0        2020-01-07 - 后退手势调整为当没有上一页时,关闭标签页。新增 “↑→↓”关闭其他页面 手势。
// @v3.5.1        2020-01-07 - 为修改过手势的小伙伴添加更新手势存储。
// @v3.6.0        2020-01-13 - 新增双击功能。双击页面,若有选中文字则百度搜索该关键字,若选中文字为网址,则打开网址页面。
// @v3.7.0        2020-01-13 - 调整双击事件的触发逻辑,使之在未限制缩放的网页或PC版网页下仍可生效。减小滑动距离判定,更加敏感,但手势路径长度为2的手势需要在600ms内完成动作,否则判定为无效。调整视频滑动进度函数。
// @v3.7.1        2020-01-14 - 修改简介说明文字。
// @v3.7.2        2020-01-15 - 调整手势触发时间。
// @3.7.3         2020-01-16 - 时间限制调整为仅有↑↓和↓↑触发,其余手势皆无限制。
// @3.7.4         2020-01-19 - 修复视频滑动的距离判定错误。
// @3.7.5         2020-01-20 - 细化视频滑动时间,更加精准。要快速到片尾,请大力滑动。
// ==/UserScript==
(function() {
    'use strict';
    var Ti=null;

    //video标签变量
    var videoEle=document.getElementsByTagName('video'),_videoEle=[],videoPlayer=null,videoNum=0;
    var oriHway='landscape-primary',oriHgamma=0,oriHbeta=0,isLock=true;

    function setVideo(){videoPlayer=this;videoOriLock();}
    function videoOriLock(){
        if(videoPlayer.videoWidth>videoPlayer.videoHeight){isLock=true;}
        else{isLock=false;screen.orientation.unlock();}
    }

    //video标签事件绑定
    function videoEvent(){
        if(videoEle.length>videoNum){
            //video事件初始化
            if(!videoNum){
                //重力感应
                window.addEventListener('deviceorientation',function(e){
                    if(isLock){
                        oriHgamma=e.gamma;
                        oriHbeta=(e.beta>0) ? e.beta : -e.beta;
                        if((oriHbeta<65 || oriHbeta>115) && (oriHgamma<-25 || oriHgamma>25)){
                            oriHway=((oriHbeta<65 && oriHgamma<-25) || (oriHbeta>115 && oriHgamma>25)) ? 'landscape-primary' : 'landscape-secondary';
                        }
                        screen.orientation.lock(oriHway);
                    }
                });
            }
            //播放video标签查找
            for(Ti=videoNum;Ti<videoEle.length;Ti++){
                videoEle[Ti].addEventListener('playing',setVideo);
                if(!videoEle[Ti].paused){
                    videoPlayer=videoEle[Ti];
                    videoOriLock();
                }
                _videoEle[Ti]=videoEle[Ti];
            }
            videoNum=videoEle.length;
        }else if(videoEle.length>0){
            //Ajax重载video标签查找
            for(Ti=0;Ti<_videoEle.length;Ti++){
                if(!_videoEle[Ti].offsetWidth>0){
                    for(Ti=0;Ti<videoEle.length;Ti++){
                        videoEle[Ti].addEventListener('playing',setVideo);
                        if(!videoEle[Ti].paused){
                            videoPlayer=videoEle[Ti];
                            videoOriLock();
                        }
                        _videoEle[Ti]=videoEle[Ti];
                    }
                    videoNum=videoEle.length;
                    break;
                }
            }
        }
    }

    //手指滑动变量
    var startX=0,startY=0,endX=0,endY=0,angX=0,angY=0,direction='',path='';
    var _startX=0,_startY=0,videoRect=null;
    var saveWords='',saveTime=0,touchTime=0,clickTime=200,nowTime=0,reg=new RegExp(/((http|https):\/\/)?[a-zA-Z]*\.?[a-zA-Z]+\.[a-zA-Z]{2,3}(\/\S)?/);

    //手指滑动事件绑定
    function sliderbackListener(){
        //手指接触屏幕
        window.addEventListener('touchstart',function(e){
            touchTime=new Date().getTime();
            startX=e.changedTouches[0].screenX;
            startY=e.changedTouches[0].screenY;
            if(videoPlayer){
                _startX=e.changedTouches[0].clientX;
                _startY=e.changedTouches[0].clientY;
            }
            if(window.getSelection().toString()){
                saveWords=window.getSelection().toString();
                saveTime=new Date().getTime();
            }
        });
        //手指滑动屏幕
        window.addEventListener('touchmove',function(e){
            e.preventDefault();
            if(e.changedTouches.length==1){
                endX=e.changedTouches[0].screenX;
                endY=e.changedTouches[0].screenY;
                angX=(endX>startX) ? endX-startX : startX-endX;
                angY=(endY>startY) ? endY-startY : startY-endY;
                if(angX>50 || angY>50){
                    if(angX>angY){direction=(endX>startX) ? '→' : '←';}
                    else{direction=(endY>startY) ? '↓' : '↑';}
                    if(path.charAt(path.length-1)!=direction){path+=direction;}
                    startX=endX;startY=endY;
                }
            }
        });
        //手指离开屏幕。
        window.addEventListener('touchend', function(e){
            nowTime=new Date().getTime();
            //双击功能
            if((nowTime-clickTime)<200){//双击判定
                if((nowTime-saveTime)<400){
                    if(!reg.test(saveWords)){
                        saveWords='https://www.baidu.com/s?wd='+saveWords;
                    }else if(saveWords.indexOf('http')<0){
                        saveWords='//'+saveWords;
                    }
                    GM_openInTab(saveWords,{active:true});
                }
                if(document.webkitFullscreenElement){document.webkitExitFullscreen();}
                else if(videoPlayer){videoPlayer.webkitRequestFullScreen();}
            }else if(path.length<1 && (nowTime-touchTime)<200){//点击判定
                clickTime=nowTime;
            }
            //滑动功能
            if(videoPlayer && path.length<2){
                //视频滑动
                videoRect=videoPlayer.getBoundingClientRect();
                if(_startX>videoRect.x && _startX<(videoRect.x+videoRect.width) && _startY>videoRect.y && _startY<(videoRect.y+videoRect.height)){
                    endX=e.changedTouches[0].clientX;
                    angX=endX-_startX;
                    if(angX>30 || angX<-30){
                        videoPlayer.currentTime+=angX*angX*angX/(25000*(1-(angX*angX/(40000+angX*angX))));
                    }
                }
            }else if(gesture[path]){
                //手势执行
                if('↑↓|↓↑'.indexOf(path)>=0 && (nowTime-touchTime)>600){}
                else{
                    try{eval(pathFn[gesture[path]]);}
                    catch(error){alert('“'+path+'” 手势执行脚本错误:\n'+error+' !');}
                }
            }
            videoEvent();
            path='';
        });
    }

    //手势功能原始数据
    var gesture={
        '↑→↓←':'打开设置',
        '→←':'后退',
        '←→':'前进',
        '↓↑':'回到顶部',
        '↑↓':'回到底部',
        '←↓':'刷新页面',
        '←↑':'新建页面',
        '→↓':'关闭页面',
        '→↑':'恢复页面',
        '↑→↓':'关闭其他页面'
    },
    pathFn={
        '打开设置':'openSet()',
        '后退':'history.go(-1);if(document.referrer==""){GM_setValue("lastTab",location.href);window.close();}',
        '前进':'history.go(1)',
        '回到顶部':'document.documentElement.scrollTop=0',
        '回到底部':'document.documentElement.scrollTop=document.documentElement.scrollHeight',
        '刷新页面':'history.go(0)',
        '新建页面':'GM_openInTab("about:blank",{active:true})',
        '关闭页面':'GM_setValue("lastTab",location.href);window.close()',
        '恢复页面':'GM_openInTab(GM_getValue("lastTab"),{active:true})',
        '关闭其他页面':'GM_setValue("closeAll", Date())'
    };

    //手势存储数据读取
    gesture=GM_getValue('gesture',gesture);
    pathFn=GM_getValue('pathFn',pathFn);

    sliderbackListener();//事件绑定

    //手势操作设置UI
    var gestureUL=null,gestureEle=null,pathEle=null,gestureName='',gesturePath='';

    function openSet(){
        //页面生成
         GM_addStyle('html{font-size:62.5% !important}'+
                     '#gestureBox{background-color:#fff;width:100%;height:100%;position:fixed;padding:0;margin:0;top:0;left:0;overflow-y:auto;z-index:999998}'+
                     '#gestureBox *{font-family:"Microsoft YaHei";margin:0;padding:0;text-align:center}'+
                     '#gestureBox h1{width:60%;height:4rem;line-height:4rem;font-size:2rem;color:#0074d9;background-color:#dee6ef;margin:1rem auto;border-radius:4rem;box-shadow:.3rem .3rem 1rem #dfdfdf}'+
                     '#gestureBox #addGesture{width:4rem;height:4rem;margin:0 auto 1rem auto;line-height:4rem;background-color:#dee6ef;color:#032e58;font-size:3rem;border-radius:4rem;box-shadow:.1rem .1rem .5rem #dfdfdf}'+
                     '#gestureBox .gestureLi{height:5rem;line-height:5rem;margin-top:1rem;width:100%;border-bottom:.3rem dashed #dfdfdf}'+
                     '#gestureBox .gestureLi p{width:38%;height:4rem;line-height:4rem;font-size:2rem;border-left:0.6rem solid;margin-left:1%;color:#ffb400;background-color:#fff1cf;float:left}'+
                     '#gestureBox .gestureLi .gesturePath{float:left;width:40%;height:4rem;background-color:#f3f3f3;color:#000;font-size:2rem;line-height:4rem;box-shadow:.1rem .1rem .5rem #ccc9c9;border-radius:1rem;margin-left:3%}'+
                     '#gestureBox .gestureLi .delGesture{width:5rem;height:4rem;line-height:4rem;border-radius:4rem;float:right;margin-right:1%;font-size:2rem;color:#f00;text-decoration:line-through}'+
                     '#gestureBox #revisePath{background-color:rgba(0,0,0,.5);width:100%;height:100%;position:fixed;top:0;left:0;overflow:hidden;z-index:999999;display:none;color:#000}'+
                     '#gestureBox #revisePath span{width:5rem;height:5rem;font-size:5rem;line-height:5rem;position:absolute}'+
                     '#gestureBox #revisePath div{position:absolute;width:30%;height:3rem;line-height:3rem;font-size:3rem;bottom:15%}'+
                     '#gestureBox #revisePath p{position:absolute;top:15%;font-size:4rem;line-height:4rem;height:4rem;width:100%}'+
                     '#gestureBox #revisePath #path{top:40%;color:#ffee03;font-size:6rem}'+
                     '#gestureBox #editGesture{background-color:#fff;width:100%;height:100%;position:fixed;top:0;left:0;overflow:hidden;z-index:999999;display:none;color:#000}'+
                     '#gestureBox #editGesture p{font-size:3rem;text-align:left;margin-top:3rem;margin-left:3rem;width:100%;height:3rem;line-height:3rem}'+
                     '#gestureBox #editGesture #gestureName{margin-top:1rem;width:80%;height:4rem;line-height:4rem;font-size:2rem;color:#000;border:0.1rem solid #dadada;border-radius:1rem;text-align:left;padding:0 1rem}'+
                     '#gestureBox #editGesture #pathFn{width:80%;margin-top:1rem;height:40%;font-size:2rem;text-align:left;line-height:2.2rem;padding:1rem;border:0.1rem solid #dadada;border-radius:1rem}'+
                     '#gestureBox #editGesture div{width:10rem;height:5rem;font-size:3rem;line-height:5rem;display:inline-block;color:#fff;background-color:#2866bd;margin:3rem 1rem 0rem 1rem}');
        Ti=document.createElement('div');
        Ti.setAttribute('id','gestureBox');
        document.body.appendChild(Ti);
        Ti.innerHTML='<h1>手势轨迹设置</h1><div id="addGesture">+</div><div id="gestureUL"></div>'+
                              '<div id="revisePath"><span style="top:0;left:0;text-align:left;">┌</span><span style="top:0;right:0;text-align:right;">┐</span><span style="bottom:0;left:0;text-align:left;">└</span><span style="bottom:0;right:0;text-align:right;">┘</span>'+
                              '<p>请滑动手指</p><p id="path"></p>'+
                              '<div id="clearPath" style="left:10%;">Clear</div><div id="cancleRevise" style="right:10%;">Cancle</div></div>'+
                              '<div id="editGesture"><p>手势名称:</p><input type="text" id="gestureName" maxlength="12" placeholder="最大输入12个字符">'+
                              '<p>手势路径脚本:</p><textarea id="pathFn" placeholder="全局参数方法说明→ (startX,startY):滑动初始坐标, (endX,endY):滑动结束坐标, videoPlayer:当前播放的video标签。"></textarea>'+
                              '<div id="saveGesture">保存</div><div id="closeEdit">关闭</div></div>';
        gestureUL=document.getElementById('gestureUL');
        pathEle=document.getElementById('path');
        init();
        //路径修改事件
        document.getElementById('revisePath').addEventListener('touchmove',function(e){
            e.stopPropagation();
            e.preventDefault();
            if(e.changedTouches.length==1){
                endX=e.changedTouches[0].screenX;
                endY=e.changedTouches[0].screenY;
                angX=(endX>startX) ? endX-startX : startX-endX;
                angY=(endY>startY) ? endY-startY : startY-endY;
                if(angX>50 || angY>50){
                    if(angX>angY){direction=(endX>startX) ? '→' : '←';}
                    else{direction=(endY>startY) ? '↓' : '↑';}
                    if(pathEle.innerHTML.charAt(pathEle.innerHTML.length-1)!=direction){pathEle.innerHTML+=direction;}
                    startX=endX;startY=endY;
                }
            }
        });
        //清除路径
        document.getElementById('clearPath').addEventListener('click',function(){pathEle.innerHTML='';});
        //修改路径
        document.getElementById('cancleRevise').addEventListener('click',function(){
            if(pathEle.innerHTML){
                delete gesture[gesturePath];
                gesture[pathEle.innerHTML]=gestureName;
                GM_setValue('gesture',gesture);
                init();
            }
            document.getElementById('revisePath').style.display='none';
        });
        //.新建手势
        document.getElementById('addGesture').addEventListener('click',function(){
            document.getElementById('gestureName').value='';
            document.getElementById('pathFn').value='';
            gestureName='';
            document.getElementById('editGesture').style.display='block';
        });
        //保存手势
        document.getElementById('saveGesture').addEventListener('click',function(){
            if(document.getElementById('gestureName').value){
                if(gestureName){
                    delete pathFn[gestureName];
                    for(Ti in gesture){
                        if(gesture[Ti]==gestureName){
                            gesture[Ti]=document.getElementById('gestureName').value;
                            break;
                        }
                    }
                }
                pathFn[document.getElementById('gestureName').value]=document.getElementById('pathFn').value;
                GM_setValue('pathFn',pathFn);
                GM_setValue('gesture',gesture);
                init();
                document.getElementById('editGesture').style.display='none';
            }else{
                alert('请输入手势名称!');
            }
        });
        //关闭编辑
        document.getElementById('closeEdit').addEventListener('click',function(){
            document.getElementById('editGesture').style.display='none';
        });
    }

    function editGesture(){
        gestureName=this.getAttribute('name');
        document.getElementById('gestureName').value=gestureName;
        document.getElementById('pathFn').value=pathFn[gestureName];
        document.getElementById('editGesture').style.display='block';
    }
    function revisePath(){
        gestureName=this.getAttribute('name');
        gesturePath=this.innerHTML;
        pathEle.innerHTML='';
        document.getElementById('revisePath').style.display='block';
    }
    function delGesture(){
        gestureName=this.getAttribute('name');
        delete pathFn[gestureName];
        for(Ti in gesture){
            if(gesture[Ti]==gestureName){
                delete gesture[Ti];
                break;
            }
        }
        GM_setValue('pathFn',pathFn);
        GM_setValue('gesture',gesture);
        init();
    }

    function init(){
        gestureUL.innerHTML='';
        for(Ti in pathFn){
            gesturePath='';
            for(gestureName in gesture){
                if(gesture[gestureName]==Ti){
                    gesturePath=gestureName;
                    break;
                }
            }
            gestureUL.innerHTML+='<div class="gestureLi"><p name="'+Ti+'">'+Ti+'</p><div class="gesturePath" name="'+Ti+'">'+gesturePath+'</div><div class="delGesture" name="'+Ti+'">删除</div></div>';
        }
        //编辑手势
        gestureEle=document.querySelectorAll('#gestureBox .gestureLi p');
        for(Ti=0;Ti<gestureEle.length;Ti++){
            gestureEle[Ti].addEventListener('click',editGesture);
        }
        //修改路径
        gestureEle=document.querySelectorAll('#gestureBox .gestureLi .gesturePath');
        for(Ti=0;Ti<gestureEle.length;Ti++){
            gestureEle[Ti].addEventListener('click',revisePath);
        }
        //删除手势
        gestureEle=document.querySelectorAll('#gestureBox .gestureLi .delGesture');
        for(Ti=0;Ti<gestureEle.length;Ti++){
            gestureEle[Ti].addEventListener('click',delGesture);
        }
    }

    GM_addValueChangeListener('closeAll',function(name, old_value, new_value, remote){if(remote)window.close();});

})();