立即注册 登录
彼岸网 返回首页

天香公主的个人空间 http://www.bian-wang.com/discuz/?10005 [收藏] [复制] [分享] [RSS] txgz999@yahoo.com

日志

在主题列表页上显示所有预览内容

热度 2已有 266 次阅读2017-11-16 11:06 PM |个人分类:Discuz

Discuz的论坛里有个很好的预览功能。在管理中心的界面->界面设置->主题列表页->关闭主题预览,选择"否"之后就开启了主题预览。在主题列表页,鼠标停留在一个主题上会出现预览按钮,点这个按钮就可以预览主题内容页的东西。网友牛肉炖土豆问可否做成进入主题列表页时,所有主题的预览都自动展开(链接)。本文讨论下这个问题。

先检查下现有功能的相关代码。首先预览按钮定义在文件 template/default/forum/forumdisplay_list.htm 里: < a class="tdpre y" href="javascript:void(0);" onclick="previewThread('{echo $thread['moved'] ? $thread[closed] : $thread[tid]}', '$thread[id]');">{lang preview}</a> 而点击这个按钮后将预览内容显示出来则是文件 static/js/forum.js 里的函数 previewThread 完成的。 var previewTbody = null, previewTid = null, previewDiv = null; function previewThread(tid, tbody) { if(!$('threadPreviewTR_'+tid)) { appendscript(JSPATH + 'forum_viewthread.js?' + VERHASH); newTr = document.createElement('tr'); newTr.id = 'threadPreviewTR_'+tid; newTr.className = 'threadpre'; $(tbody).appendChild(newTr); newTd = document.createElement('td'); newTd.colSpan = listcolspan; newTd.className = 'threadpretd'; newTr.appendChild(newTd); newTr.style.display = 'none'; previewTbody = tbody; previewTid = tid; if(BROWSER.ie) { previewDiv = document.createElement('div'); previewDiv.id = 'threadPreview_'+tid; previewDiv.style.id = 'none'; var x = Ajax(); x.get('forum.php?mod=viewthread&tid='+tid+'&inajax=1&from=preview', function(ret) { var evaled = false; if(ret.indexOf('ajaxerror') != -1) { evalscript(ret); evaled = true; } previewDiv.innerHTML = ret; newTd.appendChild(previewDiv); if(!evaled) evalscript(ret); newTr.style.display = ''; }); } else { newTd.innerHTML += '<div id="threadPreview_'+tid+'"></div>'; ajaxget('forum.php?mod=viewthread&tid='+tid+'&from=preview', 'threadPreview_'+tid, null, null, null, function() {newTr.style.display = '';}); } } else { $(tbody).removeChild($('threadPreviewTR_'+tid)); previewTbody = previewTid = null; } } 从函数的定义可见它先向服务器发送了一个AJAX请求,服务器返回了预览的内容,它再将内容显示出来。

大致上有三种可能的解决途径:
  1. 在客户端激发主题列表页上各个预览按钮的点击事件
  2. 在服务器端用curl获取预览页内容,加入显示模版
  3. 在服务器端从数据库里一次获取主题列表页上所有主题的预览内容,加入显示模版
2)和3)都是在服务器端得到预览内容,3)相比2)较为省时,但需要理解获取和处理预览内容的有关代码才能改动,不敢贸然尝试。这两种办法有一定的普适性,无涉具体的预览内容。

第一种办法:在客户端激发主题列表页上各个预览按钮的点击事件

这第一种方法可以有两种略微不同的实现方式:一是在显示主题列表页时自动加载所有预览内容,二是按需加载所有预览内容,等预览内容进入显示框时才实时加载。从效果上看,按需加载效果略好。

一。自动加载所有预览内容
1)修改文件 statis/js/forum.js,将下面这句 newTr = document.createElement('tr'); 改为 var newTr = document.createElement('tr'); 这个改动将一个整体变量改成了局部变量,这样做的目的是使得函数previewThread可以处理它调用的AJAX返回前被再次使用的情形。
2)修改文件 template/default/forum/forumdisplay_list.htm,在下面这段 </tbody> <!--{/loop}--> </table><!-- end of table "forum_G[fid]" branch 1/3 --> 的上面添加: <script type="text/javascript"> previewThread('{echo $thread['moved'] ? $thread[closed] : $thread[tid]}', '$thread[id]'); </script> 这行代码就是点击预览按钮时会执行的代码,这里我们在主题列表页显示时就直接执行了这些代码。
3)修改文件 static/js/autoloadpage.js,在下面两行代码 tableobj.replaceChild(div.childNodes[0].childNodes[0], tableobj.lastChild);$('threadlist_picstyle').innerHTML += nexts[i]; 后都加入 evalscript(nexts[i]); 这个改动是为了支持在点击“下一页》”时自动显示新加入的下一页上的主题的预览内容。

二。按需加载所有预览内容
1)修改文件 template/default/forum/forumdisplay_list.htm。模仿这里的lazyload(链接)的代码,在该文件的顶部加入 <script type="text/javascript"> function load_visible_preview(tid) { var oTR = $('previewTR_' + tid); var o = $('previewPlaceholder_' + tid); if (!oTR || !o) return; if(!elementInViewport2(o, 200)) return; oTR.parentNode.removeChild(oTR); $('previewButton_' + tid).click(); } load_visible_previews = function(e) { var ol = document.getElementsByClassName('previewTR'); for (var i = 0; i < ol.length; i++) { var tid = ol[i].id.substr(10); load_visible_preview(tid); } }; // https://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport function elementInViewport2(el, delta) { var top = el.offsetTop; var left = el.offsetLeft; var width = el.offsetWidth; var height = el.offsetHeight; while(el.offsetParent) { el = el.offsetParent; top += el.offsetTop; left += el.offsetLeft; } return ( top < (window.pageYOffset + window.innerHeight + delta) && left < (window.pageXOffset + window.innerWidth + delta) && (top + height) > (window.pageYOffset - delta) && (left + width) > (window.pageXOffset -delta) ); } window.addEventListener('scroll', load_visible_previews); </script> 接下来我们给预览按钮加个ID便于在代码里找到该按钮。将下面这句 < a class="tdpre y" href="javascript:void(0);" onclick="previewThread('{echo $thread['moved'] ? $thread[closed] : $thread[tid]}', '$thread[id]');">{lang preview}</a> 改成 < a class="tdpre y" id="previewButton_$thread[tid]" href="javascript:void(0);" onclick="previewThread('{echo $thread['moved'] ? $thread[closed] : $thread[tid]}', '$thread[id]');">{lang preview}</a> 最后我们在主题列表页显示时给每个预览内容预设一个一定高度的显示区域。和常用的lazyload图片和视频比较,我们这里无法预先知道预览内容的长度,为简单记,我们就预留一个230px高度的框。在下面这段 </tbody> <!--{/loop}--> </table><!-- end of table "forum_G[fid]" branch 1/3 --> 的上面添加: <!--{if !$_G['setting']['forumdisplaythreadpreview']}--> <tr id='previewTR_$thread[tid]' class='previewTR'></tr> <tr id='previewPlaceholder_$thread[tid]'> <td colspan="{if !$_GET['archiveid'] && $_G['forum']['ismoderator']}6{else}5{/if}" class='threadpretd' style='text-align: center;'> <img src="{IMGDIR}/loading.gif" class="vm" style="width: 30px; height: 30px; padding: 100px; " /> </td> </tr> <script type="text/javascript"> window.addEventListener('load', function() { load_visible_preview({echo $thread[tid]}); }); setTimeout(function() { load_visible_preview({echo $thread[tid]}); }, 500); </script> <!--{/if}--> 2)修改文件 statis/js/forum.js,以适当处理前面给预览内容加的显示区域。要点是一旦用户点击里预览按钮来关闭预览内容后,预览内容就不再按需显示。将函数 previewThread 的定义改成 function previewThread(tid, tbody) { if(!$('threadPreviewTR_'+tid)) { appendscript(JSPATH + 'forum_viewthread.js?' + VERHASH); var newTr = document.createElement('tr'); newTr.id = 'threadPreviewTR_' + tid; newTr.className = 'threadpre'; $(tbody).appendChild(newTr); var newTd = document.createElement('td'); newTd.colSpan = listcolspan; newTd.className = 'threadpretd'; newTr.appendChild(newTd); newTr.style.display = 'none'; var o = $('previewPlaceholder_' + tid); if(BROWSER.ie) { var previewDiv = document.createElement('div'); previewDiv.id = 'threadPreview_'+tid; previewDiv.style.id = 'none'; var x = Ajax(); x.get('forum.php?mod=viewthread&tid='+tid+'&inajax=1&from=preview', function(ret) { var evaled = false; if(ret.indexOf('ajaxerror') != -1) { evalscript(ret); evaled = true; } previewDiv.innerHTML = ret; newTd.appendChild(previewDiv); if (!evaled) evalscript(ret); if (o) $(tbody).removeChild(o); newTr.style.display = ''; }); } else { newTd.innerHTML += '<div id="threadPreview_'+tid+'"></div>'; ajaxget('forum.php?mod=viewthread&tid=' + tid + '&from=preview', 'threadPreview_' + tid, null, null, null, function () { if (o) $(tbody).removeChild(o); newTr.style.display = ''; }); } } else { $(tbody).removeChild($('threadPreviewTR_'+tid)); } } 3)修改文件 static/js/autoloadpage.js,在下面两行代码 tableobj.replaceChild(div.childNodes[0].childNodes[0], tableobj.lastChild);$('threadlist_picstyle').innerHTML += nexts[i]; 后都加入 evalscript(nexts[i]); 注记
  1. 一般lazyload用于图片和视频,这里用于lazyload预览内容,这先要用ajax去服务器那取得预览内容,取得后再要显示其中的图片和视频,所以效果不如单纯的lazyload图片和视频好。
  2. 给函数elementInViewport2加了个参数delta,用意在于让预览区域不是在即将显示在浏览器时才去lazyload,而是离浏览区足够近就去取内容。
  3. 加入previewTR_$thread的用意在于让每个预览内容只lazyload一次。由此必要是因为Discuz提供的预览按钮让用户可以展开和关闭预览内容。当用户关闭预览内容后,即便预览区域在浏览区里,我们也不应该再次去lazyload。
  4. 函数load_visible_preview用了两次:既在处理load事件里调用,又设了个定时器调用。原因是前者在最初显示日志列表页时使用,而后者在加载下一页的内容时需要。

第二种办法:在服务器端用curl获取预览页内容,加入显示模版

我们接下来要做的改动就是要把这些工作在向客户端发送主题列表页前在服务器端完成。需要修改四个文件。

1)获取预览内容
在文件 source/module/forum/forum_forumdisplay.php 的底部添加一个新函数: function get_preview_content($tid) { global $_G; $url = $_G['siteurl']."forum.php?mod=viewthread&tid=$tid&inajax=1&from=preview"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_FRESH_CONNECT, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_VERBOSE, true); curl_setopt($ch, CURLOPT_COOKIE, $_SERVER["HTTP_COOKIE"]); session_write_close(); $data = curl_exec($ch); curl_close($ch); session_start(); $data = substr($data, strpos($data,'<root><![CDATA[')+strlen('<root><![CDATA['),strlen($data)); $data = substr($data,0,strpos($data,']]></root>')); return $data; } 然后在下面这行 include template($template); 的上面添加下面这段: if(!$_G['setting']['forumdisplaythreadpreview']) { $previews = array(); foreach($_G['forum_threadlist'] as $thread) { $previews[] = get_preview_content($thread['tid']); } }
2)将预览内容嵌入显示模板
修改文件 template/default/forum/forumdisplay_list.htm,在最上方添加下面这行: <script type="text/javascript" src="{$_G[setting][jspath]}forum_viewthread.js?{VERHASH}"></script> 然后在 </tbody> <!--{/loop}--> </table><!-- end of table "forum_G[fid]" branch 1/3 --> 的上面添加下面这段: <!--{if isset($previews)}--> <tr id='threadPreviewTR_$thread[tid]' class='threadpre'> <td colspan="{if !$_GET['archiveid'] && $_G['forum']['ismoderator']}6{else}5{/if}" class='threadpretd'> <div id='threadPreview_$thread[tid]'> {echo $previews[$key]} </div> </td> </tr> <!--{/if}-->
3)调整一个变量定义使得同时加入一个网页的多个预览内容里的代码互不干扰
预览内容里的图片是通过javascript代码添加的。原来Discuz的预览内容是点击一个预览按钮,嵌入一个预览内容,那时执行该内容里的代码。而现在要依次执行多个预览内容里的代码。为了这些代码互相不干扰,需要做个小的调整。修改文件 template/default/forum/viewthread_preview.htm,将下面这行 <script type="text/javascript">zoomstatus = parseInt($_G['setting']['zoomstatus']);var imagemaxwidth = '{$_G['setting']['imagemaxwidth']}';var aimgcount = new Array();</script> 改为 <script type="text/javascript">zoomstatus = parseInt($_G['setting']['zoomstatus']);var imagemaxwidth = '{$_G['setting']['imagemaxwidth']}';if (typeof aimgcount === 'undefined') var aimgcount = new Array();</script>
4)支持在点击“下一页》”时显示新加入的图片
在主题列表页的下方有个“下一页》”按钮,点击后能将下一页里的所有主题添加到当前的主题列表里来。原来添加的内容不含代码,但在我们做了上面的修改后,新加入的内容里包含了代码。我们需要执行这些代码才能将新添加的那些主题的预览内容里的图片显示出来。为此需要在文件 static/js/autoloadpage.js 里做两处修改。在下面两行代码 tableobj.replaceChild(div.childNodes[0].childNodes[0], tableobj.lastChild);$('threadlist_picstyle').innerHTML += nexts[i]; 后都加入 evalscript(nexts[i]);

使用下来发现主题列表页显示过慢。在我的测试网站上十几个主题的版块需要六,七秒钟才能显示。其原因是在服务器端用curl去一一获取主题预览内容花费了太多时间。按网上的建议改用curl_multi如下,显示速度略有改善,但仍需约五秒钟才能显示。 if(!defined('IN_ARCHIVER')) { if(!$_G['setting']['forumdisplaythreadpreview']) $previews = get_preview_content(); include template($template); } else { include loadarchiver('forum/forumdisplay'); } function get_preview_content() { global $_G; $urls = array(); foreach($_G['forum_threadlist'] as $thread) { $urls[] = $_G['siteurl'].'forum.php?mod=viewthread&tid='.$thread['tid'].'&inajax=1&from=preview'; } session_write_close(); $previews = rolling_curl($urls); session_start(); for ($i = 0; $i < sizeof($previews); $i++) { $data = $previews[$i]; $data = substr($data, strpos($data,'<root><![CDATA[')+strlen('<root><![CDATA['),strlen($data)); $data = substr($data,0,strpos($data,']]></root>')); $previews[$i] = $data; } return $previews; } // based on http://www.onlineaspect.com/2009/01/26/how-to-use-curl_multi-without-blocking/ function rolling_curl($urls) { $previews = array(); $mapping = array(); $master = curl_multi_init(); $options = array(CURLOPT_HEADER => 0, CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_BUFFERSIZE => 4096, CURLOPT_TIMEOUT => 10, // in seconds CURLOPT_COOKIE => $_SERVER["HTTP_COOKIE"] ); // make sure the rolling window isn't greater than the # of urls $rolling_window = 6; $rolling_window = (sizeof($urls) < $rolling_window) ? sizeof($urls) : $rolling_window; for ($i = 0; $i < $rolling_window; $i++) { $ch = curl_init(); $mapping[$ch] = $i; $options[CURLOPT_URL] = $urls[$i]; curl_setopt_array($ch,$options); curl_multi_add_handle($master, $ch); } do { while(($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM); if($execrun != CURLM_OK) break; // a request was just completed -- find out which one while($done = curl_multi_info_read($master)) { $dch = $done['handle']; $info = curl_getinfo($dch); if ($info['http_code'] == 200) { $output = curl_multi_getcontent($dch); $previews[$mapping[$dch]] = $output; if ($i<sizeof($urls)) { // start a new request (it's important to do this before removing the old one) $ch = curl_init(); $mapping[$ch] = $i; $options[CURLOPT_URL] = $urls[$i++]; curl_setopt_array($ch,$options); curl_multi_add_handle($master, $ch); } // remove the curl handle that just completed curl_multi_remove_handle($master, $dch); } } } while ($running); curl_multi_close($master); return $previews; }
注:在本文中的代码中,在<符号和a字符相连的地方在两者之间加了一个不应该有的空格,以避免Discuz在保存日志时自动改变日志内容。

发表评论 评论 (12 个评论)

回复 牛肉炖土豆 2017-12-6 01:08 AM
天香公主: 我修改了这篇日志,加了些内容。讨论了两种办法:

1)在客户端加载预览内容,有两种变体。一是在网页显示时就加载所有预览内容。二是只当预览内容落在浏览器的显 ...
辛苦了,哈
1)中的二,果然,比之前效果有改善,我看到,滑动滚动条时显示余下的部分,按需加载,目前来说是最好的实现方法!
回复 天香公主 2017-12-6 12:49 AM
牛肉炖土豆: 好的,辛苦辛苦。
我修改了这篇日志,加了些内容。讨论了两种办法:
1)在客户端加载预览内容,有两种变体。一是在网页显示时就加载所有预览内容。二是只当预览内容落在浏览器的显示区域里时才加载预览内容。
2)在服务器端得到预览内容,作为网页的一部分显示。也有两种变体,一是用curl,二是用curl_multi。它的缺点是增加了在服务器端做的事,所以网页显示得慢了。
回复 牛肉炖土豆 2017-11-20 09:52 PM
天香公主:    不用这么客气吧

嗯,你的观察没错。"在列表页面载入完成后去执行这个AJAX"应该是可行的,明天我试试
好的,辛苦辛苦。
回复 天香公主 2017-11-20 01:32 AM
牛肉炖土豆: 老师,这个AJAX功能非常好,我想用到运营上去,本来以为做了图片延迟加载后,页面响应时间会变快,但还是没有效果,目前,主题列表页面加载时明显有停顿感,原因 ...
   不用这么客气吧

嗯,你的观察没错。"在列表页面载入完成后去执行这个AJAX"应该是可行的,明天我试试
回复 天香公主 2017-11-20 01:31 AM
ladyff: 看到这帖子想到一个类似的问题

discuz的门户文章是默认要分页的,能不能做到像现在常见网站一样,加个链接,点一下就在本页显示全部文章?感觉也是要通过ajax实 ...
同意,试了试可以在文章里类似的加一个"下一页>>",我等会把代码贴出来,请你看看。
回复 牛肉炖土豆 2017-11-20 12:09 AM
天香公主: 谢谢,希望多看到你写的心得。
老师,这个AJAX功能非常好,我想用到运营上去,本来以为做了图片延迟加载后,页面响应时间会变快,但还是没有效果,目前,主题列表页面加载时明显有停顿感,原因应该是,列表页面的载入和AJAX是同时进行的。

请问,能否实现这个AJAX的执行,在列表页面载入完成后去执行这个AJAX呢?或者是先只加载浏览器可视区域,滚动后再去加载?这样我想能提高加载速度?
回复 ladyff 2017-11-19 09:05 PM
看到这帖子想到一个类似的问题

discuz的门户文章是默认要分页的,能不能做到像现在常见网站一样,加个链接,点一下就在本页显示全部文章?感觉也是要通过ajax实现
回复 天香公主 2017-11-18 09:47 PM
牛肉炖土豆: 果然,昨天的问题,解决了,哈,!目前没发现其他BUG,若发现了再反馈过来,谢谢老师!这个功能非常实用,这个功能基础上,我自己研究研究如何把延迟加载加进去 ...
谢谢,希望多看到你写的心得。
回复 牛肉炖土豆 2017-11-18 09:42 PM
天香公主: 谢谢测试。按你的反馈,我修改了1)并添加了4)。我对论坛功能不熟,很容易有遗漏。而且感觉论坛功能比其它部分如日志记录文章更复杂,不容易修改。你再试试看还 ...
果然,昨天的问题,解决了,哈,!目前没发现其他BUG,若发现了再反馈过来,谢谢老师!这个功能非常实用,这个功能基础上,我自己研究研究如何把延迟加载加进去
回复 牛肉炖土豆 2017-11-18 09:28 PM
天香公主: 谢谢测试。按你的反馈,我修改了3)并添加了4)。我对论坛功能不熟,很容易有遗漏。而且感觉论坛功能比其它部分如日志记录文章更复杂,不容易修改。你再试试看还 ...
好的,这就测试,
回复 天香公主 2017-11-18 09:07 PM
牛肉炖土豆: 赞赞赞!,马上测试
谢谢测试。按你的反馈,我修改了1)并添加了4)。我对论坛功能不熟,很容易有遗漏。而且感觉论坛功能比其它部分如日志记录文章更复杂,不容易修改。你再试试看还有问题吗?
回复 牛肉炖土豆 2017-11-17 01:02 AM
赞赞赞!,马上测试

facelist doodle 涂鸦板

您需要登录后才可以评论 登录 | 立即注册

小黑屋|Archiver|彼岸网  

Powered by Discuz! X3.1 © 2001-2014 Comsenz Inc.
GMT-4, 2017-12-16 09:03 AM , Processed in 0.067584 second(s), 19 queries.

返回顶部