• 144990

    文章

  • 856

    评论

  • 13

    友链

  • 最近新加了换肤功能,大家多来逛逛吧~~~~
  • 喜欢这个网站的朋友可以加一下QQ群,我们一起交流技术。

浏览器实现滚动截屏


工作日志:实现浏览器滚动截屏,并且是指定的iframe在跨域的情况下。

首先拿到这个需求的时候,想了一下,实现滚动截屏并不难,难的具体的情况如下,
在IE浏览器的情况下,在前端实现,并且iframe是跨域的,还要自动上传服务器,万事开头难,尝试了HTML2canvas之后发现,它并不能解决跨域问题,而且不同浏览器其图片会有差异,并且最重要的一点,svg图在IE浏览器中截图是空白。
当然,遇到需求,一定要解决,研究了IE设计接口,终于实现了,整体的思路是,拿到浏览器的接口对象,使用递归遍历,找到指定的iframe,拿到iframe对象,滚动每一屏,拼接起来,最后产生的是一个长图,并且是高清位图,高清的位图占的控件大小非常大,所以使用压缩算法将其大小进行了30倍的压缩。
压缩后的图片大概在100k-500k之间,那么怎么上传呢,这个比较简单了,在内存里直接使用base64编码图片,将字符串传至服务器当中,服务器拿到字符串可以进行转码存储,至于前端显示,可以把图片直接io传输显示,也可以转base64直接由img标签进行解析。
既然思路有了,那就说说具体的实现,首先,采用任意编程语言,创建一个浏览器插件工程,这里,我采用studio,首先,捕获IE浏览器窗口句柄,这个十分简单,调用原生api就可以,代码如下:
BSTR CshotScreenCtrl::shotScreen(void)
{
POINT pNow = { 0,0 };
BSTR base64Data = L"";







if (GetCursorPos(&pNow))  // 获取鼠标当前位置
{
	pNow.x = pNow.x+200 ;
	pNow.y = pNow.y+200;
	HWND hwndPointNow = NULL;

	hwndPointNow = WindowFromPoint(pNow)->m_hWnd;  // 获取鼠标所在窗口的句柄
	if (hwndPointNow)
	{
		 string data = GetIHTMLDocument2Interface(hwndPointNow);
		 CString str = (CA2W)(data.c_str());
		 base64Data = str.AllocSysString();
	}
}
return base64Data;

}
GetIHTMLDocument2Interface是获取对应的父窗体的接口对象方法,
string GetIHTMLDocument2Interface(HWND BrowserWnd)
{
string data;
CoInitialize(NULL);




HRESULT hr;

// Explicitly load MSAA so we know if it's installed
HINSTANCE hInst = ::LoadLibrary(_T("OLEACC.DLL"));
if (hInst)
{
	LRESULT lRes; //SendMessageTimeout后的返回值,用于函数pfObjectFromLresult的第1个参数
	UINT nMsg = ::RegisterWindowMessage(_T("WM_HTML_GETOBJECT"));
	::SendMessageTimeout(BrowserWnd, nMsg, 0L, 0L, SMTO_ABORTIFHUNG, 1000, (DWORD*)&lRes);

	//获取函数pfObjectFromLresult
	LPFNOBJECTFROMLRESULT pfObjectFromLresult = (LPFNOBJECTFROMLRESULT)::GetProcAddress(hInst, "ObjectFromLresult");
	if (pfObjectFromLresult)
	{
		CComQIPtr<IHTMLDocument2> spDoc;
		hr = (*pfObjectFromLresult)(lRes, IID_IHTMLDocument, 0, (void**)&spDoc);
		if (SUCCEEDED(hr))
		{
			//获取文档接口
			CComPtr<IDispatch> spDisp;
			spDoc->get_Script(&spDisp);
			CComQIPtr<IHTMLWindow2> spWin = spDisp;
	
			spWin->get_document(&spDoc.p);

			//  Change background color to red
			//spDoc->put_bgColor(CComVariant("red"));
			CComQIPtr<IHTMLWindow2> window;
			spDoc->get_parentWindow(&window);
			RECT rect;
			::GetWindowRect(BrowserWnd, &rect);
			int nWidth = rect.right - rect.left;
			int nHeigth = rect.bottom - rect.top;
			//尝试获取iframe的窗体
			CComQIPtr<IHTMLElement> fBody;
			spDoc->get_body(&fBody);
			CComQIPtr<IHTMLFrameBase2> fBase;
			EnumFrame(spDoc, rect);

			//CaptureToImage(spDoc,rect, BrowserWnd,"C://tempImg//tmp.png");
			CompressImageQuality(L"C://tempImg//tmp.png", L"C://tempImg//tmpcompress.jpg", 50);

			ReadPhotoFile(L"C://tempImg//tmpcompress.jpg",data);

		} // else document not ready
	} // else Internet Explorer is not running
	::FreeLibrary(hInst);
} // else Active Accessibility is not installed

CoUninitialize();
return data;

}
虽然找到了父窗体的接口,但是这里不能直接拿iframe的,因为我们现在是跨域,浏览器可不会让你这么简单的跨域,毕竟如果强行跨域,那你就可以强行跨站脚本攻击了,但是道理是死的,人是活的,我们换种方法,既然我们调用拿的方法拿不到,那我们就自己取找,把父窗口转成容器接口,枚举递归遍历每一个实现的iwebborwser接口的对象,直到找到名字为我们要的那个iframe为止,代码如下:
void EnumFrame(IHTMLDocument2* pHTML,RECT rect)
{
CComQIPtr spContainer(pHTML);
if (spContainer)
{
CComPtr spEnumerator; // 注:参数OLECONTF_EMBEDDINGS // 表示列举容器内的所有嵌入对象






	HRESULT hr = spContainer->EnumObjects(OLECONTF_EMBEDDINGS, &spEnumerator);
	if (spEnumerator)
	{
		CComPtr<IUnknown> spUnk;
		while (spEnumerator->Next(1, &spUnk, NULL) == S_OK)
		{
			CComQIPtr<IWebBrowser2, &IID_IWebBrowser2> spWB(spUnk);
			spUnk = NULL;
			if (spWB)
			{   

				CComPtr<IDispatch> spDocDisp;
				hr = spWB->get_Document(&spDocDisp);
				CComQIPtr<IHTMLDocument2> spHTML(spDocDisp);

				CComQIPtr<IHTMLElement> sBody;
				spHTML->get_body(&sBody);
				long sScrollHeight = 0L;
				CComQIPtr<IHTMLElement2> pBodyElem2(sBody);
				pBodyElem2->get_scrollHeight(&sScrollHeight);

				CComPtr<IHTMLWindow2> pWnd2;
				S_OK == spHTML->get_parentWindow(&pWnd2);
				BSTR frameName;
				pWnd2->get_name(&frameName);
				string str1 = (_bstr_t)frameName;

				if ("childIframe" == str1) {
					long width = 0L;
					long height = 0L;
					spWB->get_Width(&width);
					spWB->get_Height(&height);
					CaptureToImage(spHTML, rect, NULL, "C://tempImg//tmp.png");
					break;
				}else {
					EnumFrame(spHTML, rect);
				}

			}
		}
	}
}

}
到了这里,我们已经拿到iframe,也就是我们要截图的窗体,那么接下来只要调用scrollto,并且计算坐标偏移就可以了,原理十分的简单,至于后面的代码,十分简单,就不贴出来了,到了这一步,我们已经可以跨域拿到iframe里的任何东西啦,想怎么操作就怎么操作,至于压缩算法还有转base64上传的代码百度满天飞,如果你有什么不懂,可以私信,附一张效果图吧。
在这里插入图片描述


695856371Web网页设计师②群 | 喜欢本站的朋友可以收藏本站,或者加入我们大家一起来交流技术!

0条评论

Loading...


发表评论

电子邮件地址不会被公开。 必填项已用*标注

自定义皮肤 主体内容背景
打开支付宝扫码付款购买视频教程
遇到问题联系客服QQ:419400980
注册梁钟霖个人博客