主頁 >  其他 > Unity3D ZFBrowser (EmbeddedBrowser) 插件嵌入網頁無法輸入中文問題

Unity3D ZFBrowser (EmbeddedBrowser) 插件嵌入網頁無法輸入中文問題

2020-09-10 11:20:26 其他

  網頁嵌入插件最好的應該就是ZFBrowser了, 可是使用起來也是問題多多, 現在最要命的是網頁輸入不能打中文, 作者也沒打算接入IME, 只能自己想辦法了...

  搞了半天只想到一個辦法, 就是通過Unity的IME去觸發中文輸入, 然后傳入網頁, 也就是說做一個透明的 InputField 蓋住網頁的輸入文本框, 然后在 Update 或是 onValueChanged 中把內容傳給網頁, 這樣基本就能實作中文輸入了.

  因為對前端不熟悉, 我就做了一個簡單網頁做測驗:

<html>

<head>
    <title>My first page</title>
    <style>
        body {
            margin: 0;
        }
    </style>
</head>

<body>
    <h1>Test Input</h1>
    Field1:    <input type="text" id="field1"> 
    Field2:    <input type="text" id="field2"> 
    <br>
    <br>
    <script>
        function SetInputValue(id, str) {
            document.getElementById(id).value = str;
        }
        function SubmitInput(str)
        {
            document.getElementById("field2").value = "Submited : " + str;
        }
    </script>

</body>

</html>

  這里網頁有兩個Text Area, 左邊作為輸入, 右邊作為回車后的呼叫測驗:

 

  然后Unity中直接用一個InputField放到 Field1 的位置上, 設定為透明, 通過Browser類提供的CallFunction方式呼叫就可以了:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

namespace UIModules.UITools
{
    public class BrowserInputField : MonoBehaviour
    {
        [SerializeField]
        public ZenFulcrum.EmbeddedBrowser.Browser browser;
        [SerializeField]
        public InputField input;

        [Space(10.0f)]
        [Header("設定網頁 input 函式名稱")]
        [SerializeField]
        public string SetInputFuncName = "SetInputFuncName";
        [Header("設定網頁 submit 函式名稱")]
        [SerializeField]
        public string SubmitFuncName = "SubmitFuncName";
        [Header("網頁 input id")]
        [SerializeField]
        public string InputElementID = "InputElementID";

        public bool inited { get; private set; }

        private void Awake()
        {
            this.RequireComponent<CanvasGroup>().alpha = 0.01f;
            Init();
        }

        public void Init()
        {
            if(input && (false == inited))
            {
                inited = true;
                input.RequireComponent<IME_InputFollower>();       // IME 跟隨

                StartCoroutine(CaretAccess((_caret) =>
                {
                    if(_caret)
                    {
                        var group = _caret.RequireComponent<CanvasGroup>();
                        group.alpha = 1f;
                        group.ignoreParentGroups = true;
                    }
                }));
            }
        }

        IEnumerator CaretAccess(System.Action<Transform> access)
        {
            if(input)
            {
                var caret = input.transform.Find("InputField Input Caret");
                while(caret == false && input)
                {
                    caret = input.transform.Find("InputField Input Caret");
                    yield return null;
                }
                access.Invoke(caret);
            }
        }

        void Update()
        {
            if(browser && input)
            {
                browser.CallFunction(SetInputFuncName, new ZenFulcrum.EmbeddedBrowser.JSONNode[2]
                {
                    new ZenFulcrum.EmbeddedBrowser.JSONNode(InputElementID),
                    new ZenFulcrum.EmbeddedBrowser.JSONNode(input.isFocused ? input.text : (string.IsNullOrEmpty(input.text)?input.placeholder.GetComponent<Text>().text: input.text))
                });
            }
        }
    }
}

  這里InputField它會自動生成 Caret 就是輸入標記, 為了讓他能顯示出來, 需要等待到它創建出來之后設定透明度即可. 這里省掉了IME輸入法跟隨的代碼, 那是其它功能了.

  恩, 因為字體大小不一樣, 所以Caret位置不準確, 反正是能輸入了.

 

  這是靜態的寫法, 可以手動去擺 InputField, 可是在很多情況下是不適用的, 比如 Scroll View 里面的元素, 就需要動態去獲取了, 可是由于我們無法計算出網頁 input 的位置, 所以沒法動態地去設定一個InputField來對上網頁, 如果對輸入標記沒有要求的話 (就是那個打字時候會閃的 "|" 豎杠) , 就可以通過注冊網頁 input 的 onFocus 方法, 來 focus 一個 InputField, 從而觸發輸入法, 然后再像上面一樣監測輸入就行了, 而且不需要在網頁端寫輸入函式來呼叫了, 這個函式我們應該也是可以自己注冊進去的...

  這個想法很好, 來測驗看看能不能獲取網頁中的所有 input 節點吧, 在網頁那邊寫測驗(因為我確實沒寫過網頁...) : 

// ... 省略代碼了
    Field1: <input type="text" id="field1">
    Field2: <input type="text" id="field2">
    <button type="button" onclick="Click()">Get ID</button>
    <br>
    <p id="show">Show</p>
    <script>
        function Click() {
            var inputs, index;
            var showInfo = "";
            inputs = document.getElementsByTagName('input');
            for (index = 0; index < inputs.length; ++index) {
                showInfo = showInfo + inputs[index].id + " ";
            }
            document.getElementById("show").innerHTML = showInfo;
        }
    </script>
// ...

  沒錯是能獲取ID, 那我從Unity那邊來添加這個函式試試:

    private void OnGUI()
    {
        if(GUI.Button(new Rect(500, 100, 100, 50), "TestClick"))
        {
            var script = @"
function TestClick() {
    var inputs, index;
    var array = new Array();
    inputs = document.getElementsByTagName('input');
    for (index = 0; index < inputs.length; ++index) {
        array.push(inputs[index].id);
    }
    return array;
}";
            var inputs = browser.EvalJS(script);
            if(inputs != null)
            {
                inputs.Done((_value) =>
                {
                    if(_value != null)
                    {
                        Debug.Log(_value.ToString());
                    }

                    var retVal = browser.CallFunction("TestClick");
                    if(retVal != null)
                    {
                        retVal.Done((_ret) =>
                        {
                            if(_ret != null)
                            {
                                Debug.Log(_ret.ToString());
                            }
                        });
                    }
                });
            }
        }
    }

  我創建了一個 TestClick 方法, 通過 EvalJS 解釋到網頁中, 還好這些解釋語言的套路都差不多, 只是不知道它給我回傳的是啥, 第一個解釋 js function的回傳有點意外, 居然是個空 :

  不過沒關系, 后面的函式呼叫回傳是我要的 : 

  不錯, 回傳了我要的節點名稱, 這樣函式就注冊進去然后呼叫成功了, 說明確實可以通過注入式的代碼完成呼叫, 然后我只需要把另一個設定 input 內容的代碼注入進去, 就可以隨時修改所有 input 物件了.

// 本作核心代碼
    function SetInputValue(id, str) {
        document.getElementById(id).value = https://www.cnblogs.com/tiancaiwrk/p/str;
    }

  馬上加進去看看, 先整合一下代碼把請求提取出來 : 

    public static void WebBrowserFunctionRegister(ZenFulcrum.EmbeddedBrowser.Browser browser, string function, System.Action<ZenFulcrum.EmbeddedBrowser.JSONNode> succ = null)
    {
        if(browser)
        {
            var register = browser.EvalJS(function);
            if(register != null)
            {
                register.Done((_value) =>
                {
                    if(succ != null)
                    {
                        succ.Invoke(_value);
                    }
                });
            }
        }
    }
    public static void WebBrowserFunctionCall(ZenFulcrum.EmbeddedBrowser.Browser browser, string functionname, ZenFulcrum.EmbeddedBrowser.JSONNode[] param,
        System.Action<ZenFulcrum.EmbeddedBrowser.JSONNode> result = null)
    {
        if(browser)
        {
            var retVal = param != null && param.Length > 0 ? browser.CallFunction(functionname, param) : browser.CallFunction(functionname);
            if(retVal != null)
            {
                retVal.Done((_ret) =>
                {
                    if(result != null)
                    {
                        result.Invoke(_ret);
                    }
                });
            }
        }
    }

    private void OnGUI()
    {
        if(GUI.Button(new Rect(500, 100, 100, 50), "TestClick"))
        {
            var testClick = @"
function TestClick() {
    var inputs, index;
    var array = new Array();
    inputs = document.getElementsByTagName('input');
    for (index = 0; index < inputs.length; ++index) {
        array.push(inputs[index].id);
    }
    return array;
}";
            var coreScript = @"
    function SetInputValue(id, str) {
        document.getElementById(id).value = https://www.cnblogs.com/tiancaiwrk/p/str;
    }
";
            WebBrowserFunctionRegister(browser, testClick, (_) =>
            {
                WebBrowserFunctionCall(browser, "TestClick", null, (_ret) =>
                {
                    WebBrowserFunctionRegister(browser, coreScript, (__) =>
                    {
                        var list = LitJson.JsonMapper.ToObject<List<string>>(_ret.AsJSON);
                        if(list != null)
                        {
                            foreach(var id in list)
                            {
                                WebBrowserFunctionCall(browser, "SetInputValue", new ZenFulcrum.EmbeddedBrowser.JSONNode[2] {
                                    new ZenFulcrum.EmbeddedBrowser.JSONNode(id),
                                    new ZenFulcrum.EmbeddedBrowser.JSONNode("測驗:" + id),
                                });
                            }
                        }
                    });
                });
            });
        }
    }

  因為我不確定它是不是都是異步的, 所以都用回呼的形式來做了, 結果喜人, 確實能夠正確運行了:

  幾乎成了, 下一步就是注冊一下 input 的 focus 事件, 在網頁觸發 focus 之后就創建一個 InputField 按照套路走就行了, 在 InputField 的focus取消的時候銷毀它, 就能完美解決輸入法問題了...

 -------------------------------------------------------------------------------------------------

(2020.7.7)

  之前的理論沒有問題, 不過可以更加簡化一點, 首先 ZFBrowser 決議的網頁, 它的 focus 跟 InputField 中的 focus 并不沖突, 并且在兩邊都 focus 的情況下, 網頁接收的輸入就是 Unity 呼叫的 IME, 所以就不需要同步 InputField 中的輸入到網頁那邊了, InputField 只作為啟動 IME 的入口即可.

  然后發現很多網頁中的 input 元素并不使用 id, 而是直接 class 設定了呼叫邏輯, 比較面向程序, 而且W3C標準中, 每個控制元件或者元素, 并沒有一個GUID, 這就無法通過唯一ID定位到某個元素上了 ( [對Web頁面元素的絕對唯一參考方法] https://www.cnblogs.com/birdshome/archive/2006/09/28/uniqueid_usage.html )...

  那么我們想要獲取和設定某個 input 的元素的時候, 就需要自己給沒有 id 的 input 元素添加ID了.

  然后一個元素的呼叫函式, 不像C#中的delegate那么方便, 你要添加一個唯一呼叫, 只需要洗掉原有回呼再添加即可:

    browser.onConsoleMessage -= OnConsoleMessage;
    browser.onConsoleMessage += OnConsoleMessage;

  C#怎么樣都不會錯誤添加多個同樣的回呼. 可是JS沒有這個, 有些人自己寫了相似的, 可是不是面向物件, 肯定會出錯.

 

  首先來看看怎樣給 input 元素添加 id, 然后添加 onfocus 方法給它, 讓它在焦點的時候能夠通知到 Unity 來創建 InputField 觸發 IME.

  /* 創建唯一ID代碼 */

    public const string InjectInputID_JS_Name = "InjectInputID";
    public const string InjectInputID_JS = @"
var inputID = 1;
function InjectInputID() {
    var inputs, index;
    inputs = document.getElementsByTagName('input');
    for (index = 0; index < inputs.length; ++index) {
        var rawID = inputs[index].id;
        if(rawID == null || rawID == ''){
            inputs[index].id = 'custom_input_id_' + (inputID++);
        }
    }
}";

// 某處呼叫注冊函式
WebBrowserFunctionRegister(browser, InjectInputID_JS);

  上面的注入ID代碼使用了一個全域變數 inputID, 這樣在設定時就能避免網頁動態加載出來的新元素得到同樣的ID了.

 

  /* 添加回呼事件方法 */

    public const string AddEventFunc_JS_Name = @"AddEventFunc";
    public const string eventFuncNameTemplate_JS = "EVENTFUNC";
    public const string eventTemplateName_JS = "EVENTNAME";
    public const string AddEventFuncTemplate_JS = @"
function AddEventFunc(elementID) 
{
    var tagElement = document.getElementById(elementID);
    if (tagElement != null) 
    {
        var oldFuncStr = (tagElement.EVENTFUNC + '').replace(/(\n)+|(\r\n)+/g, '');
        var rawFunc = oldFuncStr.substring(oldFuncStr.indexOf('{') + 1, oldFuncStr.indexOf('}'));
        var newFunc = function() 
        {
            eval(rawFunc);
            console.log(elementID + ':EVENTNAME');
        }
        if((newFunc + '').replace(/(\n)+|(\r\n)+/g, '') == oldFuncStr){
            return;
        }
        tagElement.EVENTFUNC = newFunc;
    }
}";

    public enum ElementEventFunc
    {
        onclick,
        onsubmit,
        onfocus
    }
    public static string GenerateAddEventFunc_JS_Code(ElementEventFunc eventFunc, string customEvent)
    {
        return GenerateAddEventFunc_JS_Code(eventFunc.ToString(), customEvent);
    }
    public static string GenerateAddEventFunc_JS_Code(string eventFunc, string customEvent)
    {
        return AddEventFuncTemplate_JS.Replace(eventFuncNameTemplate_JS, eventFunc).Replace(eventTemplateName_JS, customEvent);
    }

// 某處呼叫注冊函式
    string focusFunc_JS = GenerateAddEventFunc_JS_Code(ElementEventFunc.onfocus, ElementEventFunc.onfocus.ToString() + ":" + browser.GetHashCode().ToString());
    WebBrowserFunctionRegister(browser, focusFunc_JS);

  這里使用了一個模板來創建 function, 因為考慮到以后可能會使用到其它事件的注冊, 不一定只有 focus 的.

  說來瀏覽器的解釋執行代碼也挺神奇的, 一個函式可以直接以字串的方式獲取, 好像叫Blob, 反正就像上面代碼中的, 比如是 onfocus 函式, 那么就成了:

var oldFuncStr = (tagElement.onfocus + '').replace(/(\n)+|(\r\n)+/g, '');

  這樣就把 onfocus 的呼叫方法字串得到了, 像是下面這樣:

<input type="text" id="field1", onfocus="OnFocus(this.id)">
<script>
    function OnFocus(id){
        console.log(id);
    }
    function Test() {
        var tag  = document.getElementById('field1');
        console.log((tag.onfocus + '').replace(/(\n)+|(\r\n)+/g, ''));
    }
    Test() 
</script> 

  得到的 onfocus 字串 : 

function onfocus(event) {  OnFocus(this.id)}

  然后就是把里面的方法取出來, 封裝到新的方法里面去, 當然原有方法是字串, 必須使用 eval 來進行編譯呼叫:

    var rawFunc = oldFuncStr.substring(oldFuncStr.indexOf('{') + 1, oldFuncStr.indexOf('}'));
    var newFunc = function() 
    {
        eval(rawFunc);
        console.log(elementID + ':onfocus:XXXX');    // 這里運行時XXXX被設定為unity Browser物件的哈希值
    }
    if((newFunc + '').replace(/(\n)+|(\r\n)+/g, '') == oldFuncStr){
        return;
    }
    tagElement.onfocus = newFunc;                    // 

  newFunc 就包含了老函式呼叫和新的 Log, 我們就是以監聽 log 來發送訊息的, 中間有個比較 newFunc 和 oldFuncStr 的邏輯, 因為它不像delegate那樣可以不重復添加回呼, 并且 JS 的回呼會包含閉包資訊之類的, 如果這個添加回呼的添加了兩次, 它會造成死回圈, 我不是很清楚為什么, 所以判斷相同的回呼時不再進行添加. 這里就限定了只能添加一次回呼, 邏輯是有問題的, 不過本工程中使用上已經夠了.  

  這樣就能注冊并監聽網頁 input 元素的 onfocus 事件了. 詳細注冊方法如下, 因為網頁回呼的log必須帶有對應的網頁ID, 才能分清是哪個網頁的 input 被焦點了:

    private Dictionary<Browser, HashSet<string>> m_focusTargets = new Dictionary<Browser, HashSet<string>>();
    
    public const string GetInputs_JS_Name = "GetInputs";
    public const string GetInputs_JS = @"
function GetInputs() {
    var inputs, index;
    var array = new Array();
    inputs = document.getElementsByTagName('input');
    for (index = 0; index < inputs.length; ++index) {
        array.push(inputs[index].id);
    }
    return array;
}";
    
    private void RegisterBaseFunctions(Browser browser)
    {
        browser.onConsoleMessage -= OnFocus;
        browser.onConsoleMessage += OnFocus;

        WebBrowserFunctionRegister(browser, InjectInputID_JS);
        string focusFunc_JS = GenerateAddEventFunc_JS_Code(ElementEventFunc.onfocus, ElementEventFunc.onfocus.ToString() + ":" + browser.GetHashCode().ToString());
        WebBrowserFunctionRegister(browser, GetInputs_JS);
    }
    
    private void OnFocus(string msg, string src)
    {
        if(string.IsNullOrEmpty(msg))
        {
            return;
        }
        Debug.Log("OnFocus msg : " + msg);
        var sp = msg.Split(':');
        if(sp != null && sp.Length >= 3)
        {
            var id = sp[0];
            var hashCode = sp[2];
            switch(sp[1])
            {
                case "onfocus":
                    {
                        OnFocus(GetBrowserByHash(hashCode), id);
                    }
                    break;
            }
        }
    }
    
    public Browser GetBrowserByHash(string hashCode)
    {
        foreach(var browser in m_scanTargets.Keys)
        {
            if(browser && string.Equals(browser.GetHashCode().ToString(), hashCode, System.StringComparison.Ordinal))
            {
                return browser;
            }
        }
        return null;
    }
    
    private void OnFocus(Browser browser, string id, string text = null)
    {
        if(browser)
        {
            // ......
        }
    }    

  因為網頁會動態加載或者創建元素, 所以獲取 input 和注入回呼需要在update或者協程中不斷地獲取, 來保證每個 input 的回呼正確...

  如果 input 的 id 是 "field1", 那么回呼中傳回來的 message 就是 "field1:onfocus:XXXX" ,  XXXX就是 browser.GetHashCode().ToString() 

  在協程中去不斷檢測是否有新 input 元素:

    // 在某處呼叫
    StartCoroutine(CheckWebInput());
    
    private IEnumerator CheckWebInput()
    {
        while(true)
        {
            yield return null;
            foreach(var tags in m_focusTargets)
            {
                var browser = tags.Key;
                BrowserInputCheck(browser, tags.Value);
            }
        }
    }    
        
    // 因為網頁可能動態加載, 我們需要不斷地獲取網頁 input 元素, 來進行注冊 onfocus 回呼
    private void BrowserInputCheck(Browser browser, HashSet<string> exists)
    {
        if(browser && browser.IsLoaded)
        {
            WebBrowserFunctionCall(browser, InjectInputID_JS_Name, null, (_) =>
            {
                WebBrowserFunctionCall(browser, GetInputs_JS_Name, null, (_ret) =>
                {
                    var inputIDs = LitJson.JsonMapper.ToObject<List<string>>(_ret.AsJSON);
                    if(inputIDs != null && inputIDs.Count > 0)
                    {
                        foreach(var inputID in inputIDs)
                        {
                            if(exists.Contains(inputID) == false)
                            {
                                exists.Add(inputID);
                                WebBrowserFunctionCall(browser, AddEventFunc_JS_Name, new JSONNode[1] { new JSONNode(inputID) });
                            }
                        }
                    }
                });
            });
        }
    }

 

  當可以正常收到 onfocus 事件之后, 只需要對應創建 InputField 組件, 然后同樣把 Unity 的 Focus 給這個 InputField 就行了, 至于 html 的 blur (丟失焦點) 事件, 這里是不用監聽的, 因為 InputField 同樣會因為滑鼠操作, 鍵盤 Enter/Return 按鍵, ESC 按鍵觸發 OnEditEnd 并丟失焦點, 所以我們只需要監聽沒有丟失焦點的情況即可.

  在什么情況下 Unity的 InputField會丟失焦點而網頁 input 不會丟失焦點呢? 測驗了一下包含以下情形:

  1. Enter / Return / ESC 鍵盤都觸發了 InputField 的丟失焦點, 可是網頁并不一定會丟失焦點.

  2. 滑鼠移出網頁顯示的UI區域, 網頁會丟失焦點, 不過滑鼠移回網頁它會自動觸發 onfocus, 這都沒有問題, 可是如果用戶再次點擊網頁 input 區域, 不會再次觸發 onfocus 事件, 此時由于點擊操作 InputField 會丟失焦點.

 

  應對這些情況, 就需要做對應修改, 在 InputField 的 onEndEdit 回呼中, 添加相關測驗以及操作 (回呼經過封裝處理, 變數 BrowserInputField 包含了相關組件參考) : 

    public const string HasFocusFunc_JS_Name = "GetHasFocus";
    public const string HasFocusFunc_JS = @"
function GetHasFocus(id) {
    var target = document.getElementById(id);
    return (target != null && target.id == document.activeElement.id);
}";
    public const string CancelFocus_JS_Name = "CancelFocus";
    public const string CancelFocus_JS = @"
function CancelFocus() {
    document.activeElement.blur();
}";
    
    // 在某處進行注冊
    WebBrowserFunctionRegister(browser, HasFocusFunc_JS);
    WebBrowserFunctionRegister(browser, CancelFocus_JS);
    
    // 封裝后的 InputField.onEndEdit 回呼. BrowserInputField 包含相關組件
    private void OnEditEnd(BrowserInputField inputField)
    {
        if(inputField)
        {
            if(Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter) || Input.GetKeyDown(KeyCode.Escape))
            {
                WebBrowserFunctionCall(inputField.browser, CancelFocus_JS_Name, null);
            }
            else
            {
                var ui = MathTools.GetMouseOnUI();
                if(ui)
                {
                    var guiData = https://www.cnblogs.com/tiancaiwrk/p/ui.GetComponent();
                    if(guiData && guiData.browser == inputField.browser)
                    {
                        Core.CoroutineRoot.instance.RunWaitFrames(2, () =>
                        {
                            WebBrowserFunctionCall(inputField.browser, HasFocusFunc_JS_Name, new JSONNode[1] { new JSONNode(inputField.InputElementID) }, (_ret) =>
                            {
                                Common.DataTable value = _ret.AsJSON;
                                if((bool)value)
                                {
                                    inputField.FocusInputField();
                                }
                            });
                        });
                    }
                }
            }
        }
    }

  1. 當Enter / Return / ESC 鍵盤都觸發了 InputField 的丟失焦點, 強行對網頁當前的焦點執行 blur 操作, 禁止沒有IME的輸入繼續輸入網頁.

  2. 當滑鼠還在網頁UI區域時, 如果丟失了焦點就檢測對應ID的 input 是否也丟失了焦點, 如果沒有就重新焦點到 InputField. 

  這樣解決了焦點問題之后, 輸入 中文 / 日文 這些需要IME支持的語言就能正確輸入了, 當然如果看了源代碼知道怎樣直接觸發IME的話, 可以把 InputField 省略掉. Unity 怎樣Focus目標的代碼:

        public void FocusInputField()
        {
            if(input)
            {
                if (EventSystem.current.currentSelectedGameObject != input.gameObject)
                {
                    EventSystem.current.SetSelectedGameObject(input.gameObject, null);
                }            
                input.OnPointerClick(new PointerEventData(EventSystem.current));
            }
        }

 

  IME 跟隨在之前的帖子里 : https://www.cnblogs.com/tiancaiwrk/p/12603955.html

 

  完成這些之后, 還有一步, 就是動態創建 InputField 以及讓它跟隨 input 元素的位置, 因為 IME 會跟隨 InputField, 可是 InputField 怎樣跟隨網頁 input 元素呢? 這里當然地需要獲取 input 元素的位置資訊了, html 提供了相關代碼 : 

    // 獲取頁面大小
    var width = document.body.clientWidth;
    var height = document.body.clientHeight;
    // 獲取元素位置以及大小
    var tag = document.getElementById(id);
    var rect = tag.getBoundingClientRect();

  頁面大小可以理解為整個 html 渲染區域的大小, 它跟UGUI的大小有對應關系, 比如下圖顯示頁面大小1000x1000, 在UGUI上它渲染的大小就是UI的Rect Size:

  這需要關閉自動修改尺寸才能實作:

  如果是這種情況, 那么計算 input 元素的位置就需要經過二次轉換了, 首先UGUI中螢屏位置左下角為(0,0), 而 html 中左上角才是(0,0):

  1. 需要先獲取頁面大小, 然后獲取 input 在頁面中的位置

  2. 獲取渲染UI的大小, 并獲取UI左上角的坐標位置

  3. 計算 input 在頁面中的歸一化位置 [0,1], 然后相對UI左上角位置獲取偏移量, 并轉換到當前UI Pivot的相對偏移量

  4. UI位置加上偏移量就是 input 元素的位置了, 可以在Update中進行跟隨操作 (這是網頁渲染到UI面板, 面板可移動的情況)

  5. 如果頁面內的元素也是可以移動的, 比如頁面中有Scroll可以滑動input, 就需要隨時重新計算偏移量了

 

  以上面的觸發Focus的位置作為入口, 創建InputField, 并計算跟隨偏移量 : 

    const string GetElementRect_JS_Name = "GetElementRect";
    const string GetElementRect_JS = @"
function GetElementRect(id)
{
    var tag = document.getElementById(id);
    var rect = tag.getBoundingClientRect();
    var region = {};
    region['x'] = rect.left;
    region['y'] = rect.top;
    region['width'] = Math.abs(rect.right - rect.left);
    region['height'] = Math.abs(rect.top - rect.bottom);
    return region;
}";

    const string GetHtmlBodySize_JS_Name = "GetHtmlBodySize";
    const string GetHtmlBodySize_JS = @"
function GetHtmlBodySize()
{    
    var region = {};
    region['x'] = document.body.clientWidth;
    region['y'] = document.body.clientHeight;
    return region;
}";
    
    // 在某處注冊代碼
    WebBrowserFunctionRegister(browser, GetElementRect_JS);
    WebBrowserFunctionRegister(browser, GetHtmlBodySize_JS);
    
    private void OnFocus(Browser browser, string id, string text = null)
    {
        if(browser)
        {
            var dict = m_scanTargets.GetValue(browser);
            var browserInputField = dict.TryGetNullableValue(id);
            if(browserInputField == false)
            {
                // 創建代碼, 省略
            }
            if(browserInputField)
            {
                InputFieldFollowWebInput(browserInputField);    // 設定跟隨
                browserInputField.gameObject.SetActive(true);
                browserInputField.FocusInputField();            // Focus 到InputField, 觸發IME
            }
        }
    }
    
    private void InputFieldFollowWebInput(BrowserInputField inputField)
    {
        if(inputField && inputField.browser)
        {
            inputField.transform.position = Input.mousePosition;
            WebBrowserFunctionCall(inputField.browser, GetElementRect_JS_Name,
                new ZenFulcrum.EmbeddedBrowser.JSONNode[1] { new ZenFulcrum.EmbeddedBrowser.JSONNode(inputField.InputElementID) }, (_rect) =>
                {
                    var inputRect = LitJson.JsonMapper.ToObject<Rect>(_rect.AsJSON);
                    WebBrowserFunctionCall(inputField.browser, GetHtmlBodySize_JS_Name, null, (_size) =>
                    {
                        var htmlSize = LitJson.JsonMapper.ToObject<Vector2>(_size.AsJSON);
                        var tag = inputField.browser.GetComponent<Modules.BrowserRenderTarget>();
                        if(tag)
                        {
                            var rect = tag.browserGUINetData.transform as RectTransform;
                            if(rect)
                            {
                                var uiSize = rect.rect.size;
                                var anchorPos = rect.position + MathTools.Multiple(new Vector2(0, 1) - rect.pivot, uiSize).UpGrade(MathTools.VecAxis.Z);
                                var x = (inputRect.x / htmlSize.x) * uiSize.x;
                                var y = (inputRect.y / htmlSize.y) * uiSize.y;
                                var inputPos = anchorPos + new Vector3(x, -y, 0);
                                var offset = MathTools.Multiple(inputPos - rect.position, rect.lossyScale);
                                Tools.UIObjectFollow.instance.AddFollowInfo(inputField.transform as RectTransform, rect, offset);
                            }
                        }
                    });
                });
        }
    }

 

(2020.07.09)

  上面的邏輯仍舊是對頁面元素位置不變而計算的( UGUI可以移動位置, 因為輸入法可以跟隨 ), 說到頁面元素位置會變化的情況, 以最簡單的例子為例 : 當界面大小與網頁大小不同時, 有 Scroll 拖動條的情況.

  先看看網頁代碼, 控制了元素大小使得頁面大小比較大:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Scroll</title>
    <style>
        #top {
            height: 500px;
            color: #FFF;
            background-color: #000000;
        }
        #bottom {
            height: 100px;
            color: rgb(0, 0, 0);
            background-color: #ffffff;
        }
    </style>
</head>
<body>
    <div>
        Input1 :
        <input id="field1">
        <button id="top" onclick="GetInfo()">Button</button>
        <br> Input2 :
        <input id="bottom">
    </div>

    <script type="text/javascript">
        function GetInfo() {
            console.log("網頁大小 : " + document.body.clientWidth + " x " + document.body.clientHeight);
        }
    </script>
</body>
</html>

  那么在Unity中運行時, 有UGUI大小, 以及Browser設定頁面大小, 以及實際網頁大小:

  ( UGUI 400 x 400 )

  ( Browser 300 x 500 )

  網頁Log列印出 267 x 627, 所以在頁面上可以看到因為 Browser的大小 Height 500 < 627, 所以出現了拖動條, 而且映射到了UGUI 400 x 400 的RawImage上, 所以產生了縮放.

  因為我制作的 InputField 預制體是以左上角對齊的, 所以只需要計算出網頁 input 的左上角坐標即可:

  

  獲取 input 在html中坐標的時候, 它給的坐標是已經計算過Scroll之后的坐標, 所以直接使用即可:

  看到它們的 top 位置因為 Scroll 拖動而改變了, 這樣就省了我們要去計算 Scroll 造成的偏移了.

  下面的代碼就是怎樣計算 input 位置到 UGUI 位置的演算法了:

    private void InputFieldFollowWebInput(BrowserInputField inputField)
    {
        if(inputField && inputField.browser)
        {
            WebBrowserFunctionCall(inputField.browser, GetElementRect_JS_Name,
                new ZenFulcrum.EmbeddedBrowser.JSONNode[1] { new ZenFulcrum.EmbeddedBrowser.JSONNode(inputField.InputElementID) }, (_rect) =>
                {
                    var inputRect = LitJson.JsonMapper.ToObject<Rect>(_rect.AsJSON);
                    InputFieldFollowWebInput(inputField, inputRect);
                });
        }
    }
    private void InputFieldFollowWebInput(BrowserInputField inputField, Rect inputRect)
    {
        if(inputField && inputField.browser)
        {
            var tag = inputField.browser.GetComponent<Modules.BrowserRenderTarget>();
            if(tag && tag.renderTarget)
            {
                var rect = tag.renderTarget.rectTransform;
                if(rect)
                {
                    var uiSize = rect.rect.size;        // UGUI size
                    var anchorPos = rect.position + MathTools.Multiple(new Vector2(0, 1) - rect.pivot, uiSize).UpGrade(MathTools.VecAxis.Z); // ugui top left pos
                    var browserSize = inputField.browser.Size;  // uiSize equals to how large the browserSize is

                    var x_normalized = (inputRect.x / browserSize.x);     // normalized pos_x
                    var y_normalized = (inputRect.y / browserSize.y);     // normalized pos_y
                    var x = (x_normalized) * uiSize.x;
                    var y = (y_normalized) * uiSize.y;
                    var inputPos = anchorPos + new Vector3(x, -y, 0);
                    var offset = MathTools.Multiple(inputPos - rect.position, rect.lossyScale);
                    var inputPosUI = rect.position + offset;
                    inputField.transform.position = inputPosUI;
                }
            }
        }
    }

  感覺比之前的演算法反而更簡單了? 沒錯, 因為使用了統一歸一化演算法, 本來 x_normalized , y_normalized 需要使用 html 網頁大小來進行計算的:

    var x_normalized = (inputRect.x / htmlSize.x) * (htmlSize.x / browserSize.x);  // normalized pos_x
    var y_normalized = (inputRect.y / htmlSize.y) * (htmlSize.y / browserSize.y);  // normalized pos_y

  不過剛好因為統一歸一化被消除了, 并且 html 的元素位置資訊包含了 Scroll 之后的資訊, 所以其他資訊都不需要了, 只需要 input 元素的位置資訊就夠了. 把計算要放在Update之中才行了, 因為要隨時計算坐標......

  經過這些程序, 基本上坐標沒有問題了, 因為運行時 InputField 是完全透明的, 所以大小之類的都無所謂了, 只要去除 raycast 相關的讓人無法選中即可.

 ----------------------- 簡化了一些代碼 --------------------------

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

namespace UIModules.UITools
{
    /// <summary>
    /// embered input to browser input, active the IME
    /// </summary>
    public class BrowserInputField : MonoBehaviour
    {
        [SerializeField]
        public ZenFulcrum.EmbeddedBrowser.Browser browser;
        [SerializeField]
        public InputField input;

        [Space(10.0f)]
        [Header("網頁 input id")]
        [SerializeField]
        public string InputElementID = "InputElementID";

        public Core.Event<BrowserInputField> onEditEnd = new Core.Event<BrowserInputField>();
        public bool inited { get; private set; }

        #region Mono Funcs
        private void Awake()
        {
            this.RequireComponent<CanvasGroup>().alpha = 0.0f;
            Init();
        }
        #endregion

        #region Main Funcs
        public void Init()
        {
            if(input && (false == inited))
            {
                inited = true;
                input.RequireComponent<IME_InputFollower>();
                input.RequireComponent<InputFocusOutOfControl>();

                input.onEndEdit.AddListener(OnEndEdit);
            }
        }
        public void FocusInputField()
        {
            if(input)
            {
                if (EventSystem.current.currentSelectedGameObject != input.gameObject)
                {
                    EventSystem.current.SetSelectedGameObject(input.gameObject, null);
                }            
                input.OnPointerClick(new PointerEventData(EventSystem.current));
            }
        }
        #endregion

        #region Events
        private void OnEndEdit(string txt)
        {
            onEditEnd.Invoke(this);
        }
        #endregion

    }
}
BrowserInputField
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

namespace UIModules.UITools
{
    using ZenFulcrum.EmbeddedBrowser;

    /// <summary>
    /// AutoBrowserInputField can auto fit html input element
    /// </summary>
    public class AutoBrowserInputField : SingletonComponent<AutoBrowserInputField>
    {
        private Dictionary<Browser, Dictionary<string, BrowserInputField>> m_scanTargets = new Dictionary<Browser, Dictionary<string, BrowserInputField>>();
        private Dictionary<Browser, HashSet<string>> m_focusTargets = new Dictionary<Browser, HashSet<string>>();

        public UIModules.UITools.BrowserInputField inputTemplate
        {
            get
            {
                return UIManager.instance.Get<BrowserInputFieldPanel>("UI/CommonUI/BrowserInputFieldPanel").browserInputFieldTemplate;
            }
        }

        #region Overrides
        protected override void Initialize()
        {
        }
        protected override void UnInitialize()
        {
        }
        #endregion

        #region Mono Funcs
        private void Update()
        {
            CheckWebInput();
        }
        #endregion

        #region Main Funcs
        public void AddAutoInputBrowser(Browser browser)
        {
            if(browser)
            {
                RemoveAutoInputBrowser(browser);
                RegisterBaseFunctions(browser);
            }
        }
        public Browser GetBrowserByHash(string hashCode)
        {
            foreach(var browser in m_scanTargets.Keys)
            {
                if(browser && string.Equals(browser.GetHashCode().ToString(), hashCode, System.StringComparison.Ordinal))
                {
                    return browser;
                }
            }
            return null;
        }

        public void RemoveAutoInputBrowser(Browser browser)
        {
            if(browser)
            {
                var dict = m_scanTargets.TryGetNullableValue(browser);
                if(dict != null)
                {
                    foreach(var field in dict.Values)
                    {
                        if(field)
                        {
                            GameObject.Destroy(field.gameObject);
                        }
                    }
                    m_scanTargets.Remove(browser);
                }
                m_focusTargets.Remove(browser);
            }
        }

        private void CheckWebInput()
        {
            foreach(var tags in m_focusTargets)
            {
                var browser = tags.Key;
                BrowserInputCheck(browser, tags.Value);
            }
            foreach(var tags in m_scanTargets.Values)
            {
                foreach(var browserInputField in tags.Values)
                {
                    InputFieldFollowWebInput(browserInputField);
                }
            }
        }
        #endregion

        #region Events
        private void OnFocus(string msg, string src)
        {
            if(string.IsNullOrEmpty(msg))
            {
                return;
            }
            Debug.Log("OnFocus msg : " + msg);
            var sp = msg.Split(':');
            if(sp != null && sp.Length >= 3)
            {
                var id = sp[0];
                var hashCode = sp[2];
                switch(sp[1])
                {
                    case "onfocus":
                        {
                            OnFocus(GetBrowserByHash(hashCode), id);
                        }
                        break;
                }
            }
        }
        private void OnFocus(Browser browser, string id, string text = null)
        {
            if(browser)
            {
                var dict = m_scanTargets.GetValue(browser);
                var browserInputField = dict.TryGetNullableValue(id);
                if(browserInputField == false)
                {
                    browserInputField = UIManager.CopyItem<BrowserInputField>(inputTemplate.transform as RectTransform);
                    browserInputField.gameObject.name = id;
                    dict[id] = browserInputField;

                    browserInputField.browser = browser;
                    browserInputField.InputElementID = id;

                    browserInputField.Init();

                    browserInputField.onEditEnd.AddListener(OnEditEnd);
                }
                if(browserInputField)
                {
                    if(browserInputField.input)
                    {
                        browserInputField.input.text = string.Empty;
                    }
                    InputFieldFollowWebInput(browserInputField);
                    browserInputField.gameObject.SetActive(true);
                    browserInputField.FocusInputField();
                }
            }
        }
        private void OnEditEnd(BrowserInputField inputField)
        {
            if(inputField)
            {
                if(Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter) || Input.GetKeyDown(KeyCode.Escape))
                {
                    WebBrowserFunctionCall(inputField.browser, CancelFocus_JS_Name, null);
                }
                else
                {
                    var ui = MathTools.GetMouseOnUI();
                    if(ui)
                    {
                        var guiData = https://www.cnblogs.com/tiancaiwrk/p/ui.GetComponent();
                        if(guiData && guiData.browser == inputField.browser)
                        {
                            Core.CoroutineRoot.instance.RunWaitFrames(2, () =>
                            {
                                WebBrowserFunctionCall(inputField.browser, HasFocusFunc_JS_Name, new JSONNode[1] { new JSONNode(inputField.InputElementID) }, (_ret) =>
                                {
                                    Common.DataTable value = _ret.AsJSON;
                                    if((bool)value)
                                    {
                                        inputField.FocusInputField();
                                    }
                                });
                            });
                        }
                    }
                }
            }
        }
        #endregion

        #region Help Funcs
        private void RegisterBaseFunctions(Browser browser)
        {
            m_scanTargets[browser] = new Dictionary<string, BrowserInputField>();
            m_focusTargets[browser] = new HashSet<string>();

            browser.onConsoleMessage -= OnFocus;
            browser.onConsoleMessage += OnFocus;

            WebBrowserFunctionRegister(browser, SetInputValue_JS);
            WebBrowserFunctionRegister(browser, HasFocusFunc_JS);
            WebBrowserFunctionRegister(browser, CancelFocus_JS);
            WebBrowserFunctionRegister(browser, InjectInputID_JS);
            string focusFunc_JS = GenerateAddEventFunc_JS_Code(ElementEventFunc.onfocus, ElementEventFunc.onfocus.ToString() + ":" + browser.GetHashCode().ToString());
            WebBrowserFunctionRegister(browser, focusFunc_JS);
            WebBrowserFunctionRegister(browser, GetInputs_JS);

            WebBrowserFunctionRegister(browser, GetElementRect_JS);
        }
        private void BrowserInputCheck(Browser browser, HashSet<string> exists)
        {
            if(browser && browser.IsLoaded)
            {
                WebBrowserFunctionCall(browser, InjectInputID_JS_Name, null, (_) =>
                {
                    WebBrowserFunctionCall(browser, GetInputs_JS_Name, null, (_ret) =>
                    {
                        var inputIDs = LitJson.JsonMapper.ToObject<List<string>>(_ret.AsJSON);
                        if(inputIDs != null && inputIDs.Count > 0)
                        {
                            foreach(var inputID in inputIDs)
                            {
                                if(exists.Contains(inputID) == false)
                                {
                                    exists.Add(inputID);
                                    WebBrowserFunctionCall(browser, AddEventFunc_JS_Name, new JSONNode[1] { new JSONNode(inputID) });
                                }
                            }
                        }
                    });
                });
            }
        }

        private void InputFieldFollowWebInput(BrowserInputField inputField)
        {
            if(inputField && inputField.browser)
            {
                WebBrowserFunctionCall(inputField.browser, GetElementRect_JS_Name,
                    new ZenFulcrum.EmbeddedBrowser.JSONNode[1] { new ZenFulcrum.EmbeddedBrowser.JSONNode(inputField.InputElementID) }, (_rect) =>
                    {
                        var inputRect = LitJson.JsonMapper.ToObject<Rect>(_rect.AsJSON);
                        InputFieldFollowWebInput(inputField, inputRect);
                    });
            }
        }
        private void InputFieldFollowWebInput(BrowserInputField inputField, Rect inputRect)
        {
            if(inputField && inputField.browser)
            {
                var tag = inputField.browser.GetComponent<Modules.BrowserRenderTarget>();
                if(tag && tag.renderTarget)
                {
                    var rect = tag.renderTarget.rectTransform;
                    if(rect)
                    {
                        var uiSize = rect.rect.size;        // UGUI size
                        var anchorPos = rect.position + MathTools.Multiple(new Vector2(0, 1) - rect.pivot, uiSize).UpGrade(MathTools.VecAxis.Z); // ugui top left pos
                        var browserSize = inputField.browser.Size;  // uiSize equals to how large the browserSize is

                        var x_normalized = (inputRect.x / browserSize.x);     // normalized pos_x
                        var y_normalized = (inputRect.y / browserSize.y);     // normalized pos_y
                        var x = (x_normalized) * uiSize.x;
                        var y = (y_normalized) * uiSize.y;
                        var inputPos = anchorPos + new Vector3(x, -y, 0);
                        var offset = MathTools.Multiple(inputPos - rect.position, rect.lossyScale);
                        var inputPosUI = rect.position + offset;
                        inputField.transform.position = inputPosUI;
                    }
                }
            }
        }

        public static void WebBrowserFunctionRegister(ZenFulcrum.EmbeddedBrowser.Browser browser, string function,
            System.Action<ZenFulcrum.EmbeddedBrowser.JSONNode> succ = null)
        {
            if(browser)
            {
                var register = browser.EvalJS(function);
                if(register != null)
                {
                    register.Done((_value) =>
                    {
                        if(succ != null)
                        {
                            succ.Invoke(_value);
                        }
                    });
                }
            }
        }
        public static void WebBrowserFunctionCall(ZenFulcrum.EmbeddedBrowser.Browser browser, string functionname, ZenFulcrum.EmbeddedBrowser.JSONNode[] param,
            System.Action<ZenFulcrum.EmbeddedBrowser.JSONNode> result = null)
        {
            if(browser)
            {
                var retVal = param != null && param.Length > 0 ? browser.CallFunction(functionname, param) : browser.CallFunction(functionname);
                if(retVal != null)
                {
                    retVal.Done((_ret) =>
                    {
                        if(result != null)
                        {
                            result.Invoke(_ret);
                        }
                    });
                }
            }
        }
        #endregion


        #region 代碼注入相關
        #region JS Codes
        public const string GetInputs_JS_Name = "GetInputs";
        public const string GetInputs_JS = @"
function GetInputs() {
    var inputs, index;
    var array = new Array();
    inputs = document.getElementsByTagName('input');
    for (index = 0; index < inputs.length; ++index) {
        array.push(inputs[index].id);
    }
    return array;
}";

        public const string InjectInputID_JS_Name = "InjectInputID";
        public const string InjectInputID_JS = @"
var inputID = 1;
function InjectInputID() {
    var inputs, index;
    inputs = document.getElementsByTagName('input');
    for (index = 0; index < inputs.length; ++index) {
        var rawID = inputs[index].id;
        if(rawID == null || rawID == ''){
            inputs[index].id = 'custom_input_id_' + (inputID++);
        }
    }
}";

        public const string SetInputValue_JS_Name = "SetInputValue";
        public const string SetInputValue_JS = @"
function SetInputValue(id, str) {
    var target = document.getElementById(id);
    if (target != null) { target.value = https://www.cnblogs.com/tiancaiwrk/p/str; }
}";

        public const string HasFocusFunc_JS_Name = "GetHasFocus";
        public const string HasFocusFunc_JS = @"
function GetHasFocus(id) {
    var target = document.getElementById(id);
    return (target != null && target.id == document.activeElement.id);
}";

        public const string CancelFocus_JS_Name = "CancelFocus";
        public const string CancelFocus_JS = @"
function CancelFocus() {
    document.activeElement.blur();
}";

        public const string AddEventFunc_JS_Name = @"AddEventFunc";
        public const string eventFuncNameTemplate_JS = "EVENTFUNC";
        public const string eventTemplateName_JS = "EVENTNAME";
        public const string AddEventFuncTemplate_JS = @"
function AddEventFunc(elementID) 
{
    var tagElement = document.getElementById(elementID);
    if (tagElement != null) 
    {
        var oldFuncStr = (tagElement.EVENTFUNC + '').replace(/(\n)+|(\r\n)+/g, '');
        var rawFunc = oldFuncStr.substring(oldFuncStr.indexOf('{') + 1, oldFuncStr.indexOf('}'));
        var newFunc = function() 
        {
            eval(rawFunc);
            console.log(elementID + ':EVENTNAME');
        }
        if((newFunc + '').replace(/(\n)+|(\r\n)+/g, '') == oldFuncStr){
            return;
        }
        tagElement.EVENTFUNC = newFunc;
    }
}";

        const string GetElementRect_JS_Name = "GetElementRect";
        const string GetElementRect_JS = @"
function GetElementRect(id)
{
    var tag = document.getElementById(id);
    var rect = tag.getBoundingClientRect();
    var region = {};
    region['x'] = rect.left;
    region['y'] = rect.top;
    region['width'] = Math.abs(rect.right - rect.left);
    region['height'] = Math.abs(rect.top - rect.bottom);
    return region;
}";

        #endregion

        public enum ElementEventFunc
        {
            onclick,
            onsubmit,
            onfocus
        }
        public static string GenerateAddEventFunc_JS_Code(ElementEventFunc eventFunc, string customEvent)
        {
            return GenerateAddEventFunc_JS_Code(eventFunc.ToString(), customEvent);
        }
        public static string GenerateAddEventFunc_JS_Code(string eventFunc, string customEvent)
        {
            return AddEventFuncTemplate_JS.Replace(eventFuncNameTemplate_JS, eventFunc).Replace(eventTemplateName_JS, customEvent);
        }
        #endregion
    }
}
AutoBrowserInputField
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

namespace UIModules
{
    public class BrowserInputFieldPanel : UIBase
    {
        [SerializeField]
        public UIModules.UITools.BrowserInputField browserInputFieldTemplate;
    }
}
BrowserInputFieldPanel

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/2021.html

標籤:其他

上一篇:運營一個游戲禮包的小程式

下一篇:五年了還在原地踏步咋整?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more