我页面上有个字符串过长需要用省略号代替的功能,现在我用strlen和substr实现,发现中文和英文截取文字长度不一样,导致中文截取的过少,英文的截取的和设置的长度的一样。有没有什么好的方法统一中文和英文一样!表示无语啊!
我页面上有个字符串过长需要用省略号代替的功能,现在我用strlen和substr实现,发现中文和英文截取文字长度不一样,导致中文截取的过少,英文的截取的和设置的长度的一样。有没有什么好的方法统一中文和英文一样!表示无语啊!
让多余的字符串显示为省略号,截取的方式是一种很落后的方式,而且字符串截取对于中文和英文截取结果不一样。HTML5中可以直接通过css来控制:
overflow: hidden; //溢出部分影藏
white-space: nowrap; //文本不进行换行
text-overflow: ellipsis; //当文本溢出包含元素时显示省略号
这三个组合使用即可。
/**
* 字符串截取方法(支持中英文,截取长度包含省略符)
* @param string $string 字符串
* @param integer $length 截取长度
* @param string $dot 省略符
* @param string $charset 编码
* @return string
*/
function strCut($string, $length, $dot = '...', $charset = 'UTF-8') {
$charset = 'UTF-8';
$strlen = strlen($string);
if($strlen <= $length) return $string;
$string = str_replace(
array(' ',' ', '&', '"', '\'', '“', '”', '—', '<', '>', '·', '…'),
array(' ',' ', '&', '"', "'", '“', '”', '—', '<', '>', '·', '…'),
$string
);
$strcut = '';
if (strtolower($charset) == 'utf-8') {
$length = intval($length-strlen($dot)-$length/3);
$n = $tn = $noc = 0;
while ($n < strlen($string)) {
$t = ord($string[$n]);
if ($t == 9 || $t == 10 || (32 <= $t && $t <= 126)) {
$tn = 1; $n++; $noc++;
} elseif(194 <= $t && $t <= 223) {
$tn = 2; $n += 2; $noc += 2;
} elseif(224 <= $t && $t <= 239) {
$tn = 3; $n += 3; $noc += 2;
} elseif(240 <= $t && $t <= 247) {
$tn = 4; $n += 4; $noc += 2;
} elseif(248 <= $t && $t <= 251) {
$tn = 5; $n += 5; $noc += 2;
} elseif($t == 252 || $t == 253) {
$tn = 6; $n += 6; $noc += 2;
} else {
$n++;
}
if ($noc >= $length) {
break;
}
}
if ($noc > $length) {
$n -= $tn;
}
$strcut = substr($string, 0, $n);
$strcut = str_replace(
array('∵', '&', '"', "'", '“', '”', '—', '<', '>', '·', '…'),
array(' ', '&', '"', '\'', '“', '”', '—', '<', '>', '·', '…'),
$strcut
);
} else {
$dotlen = strlen($dot);
$maxi = $length - $dotlen - 1;
$current_str = '';
$search_arr = array('&',' ', '"', "'", '“', '”', '—', '<', '>', '·', '…','∵');
$replace_arr = array('&',' ', '"', '\'', '“', '”', '—', '<', '>', '·', '…',' ');
$search_flip = array_flip($search_arr);
for ($i = 0; $i < $maxi; $i++) {
$current_str = ord($string[$i]) > 127 ? $string[$i].$string[++$i] : $string[$i];
if (in_array($current_str, $search_arr)) {
$key = $search_flip[$current_str];
$current_str = str_replace($search_arr[$key], $replace_arr[$key], $current_str);
}
$strcut .= $current_str;
}
}
return $strcut.$dot;
}
推荐使用这个方法来避免一下尴尬:
(1) substr截取中文会出现乱码的情况:
$string = '中文字符中文字符';
var_dump(substr($string, 0, 10));
结果:string(10) "中文字�"
原因:中文占3个字符,substr
可能会把某个中文截取了一部分,使中文乱码。
(2) mb_substr截取会出现字符太长的情况:
$string = '中文字符englishword';
var_dump(mb_substr($string, 0, 10));
结果:string(18) "中文字符englis"
原因:中文占3个字符,输出结果的字符串实际占18个字符,并不是期望的10个字符。
使用情景:
微信支付商品名称有128个字符限制,在UTF-8编码下,中英文字符的总字符长度要控制在128个以内,个人觉得用这个方法比较合适。
其实题主的问题是:中文英文截取的长度一样,这里的长度并非指字符长度(Length、Byte),而是像素宽度(Width)
此理论与UTF-8中汉字是3个字符无关,其实某些生僻的汉字、Emoji占有4个字符。
UTF-8最长是6个字符长(1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx)。
按照汉字(东亚字系)的字体设计,一般情况下 1个汉字 ≈ 2个英文(数字、符号等) 的 像素宽度
如:
1a 12ab 123abc 1234abcd
汉 汉字 汉字汉 汉字汉字
可以看到1个汉字 ≈ 2个英文的width
在早期的网站中,一般使用SimSun(宋体),在SimSun的设计中,英文的宽度 == 1/2的汉字。
随着互联网的发展,一般的字体已经不能满足大家的需要,所以字体百花齐放的今天,只能 ≈ (约等于)
比如segmentfault的字符方案中,英文多出了1个宽度,但是不影响整体效果
实现
前端:(这是最好的方案)
overflow: hidden; //此句必须
white-space: nowrap; //对于无需换行的场景,可以设置width/height为固定值
text-overflow: ellipsis; //此行必须,但是Firefox部分版本不兼容
后端: mb_substr,cutStr(能署名代码来自于Discuz!可以吗?)的答案,都是错误的,这些得到的结果汉字宽度大于英文的宽度
请查看以下代码:以UTF-8为例子
/**
* 移除字符串的BOM
*
* @param string $str 输入字符串
* @return string 输出字符串
*/
function removeBOM($str)
{
$str_3 = substr($str, 0, 3);
if ($str_3 == pack('CCC',0xef,0xbb,0xbf)) //utf-8
return substr($str, 3);
return $str;
}
/**
* 按UTF-8分隔为数组,效率比MB_Substr高
* 0xxxxxxx
* 110xxxxx 10xxxxxx
* 1110xxxx 10xxxxxx 10xxxxxx
* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
* 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
* 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
*
* @param string $str 输入utf-8字符串
* @return array 返回成一段数组
*/
function str_split_utf8($str)
{
return preg_match_all('/./u', removeBOM($str), $out) ? $out[0] : FALSE;
}
/**
* 按非ascii字符占有几个字宽的方式切分字符串,并且不会将汉字切成半个
* 所谓字宽是指,使用默认字体显示时,非ascii字符相比英文字符所占大小,比如:宋体、微软雅黑中,汉字占两个宽度
* @example $ansi_width = 2 表示汉字等非英文字符按照两个字宽长度
* @example $ansi_width = 1 表示所有字符按一个字宽长度
*
* @param string $string 原始字符
* @param integer $offset 开始偏移,使用方法和substr一样,可以为负数
* @param integer $length 长度,使用方法和substr一样,可以为负数
* @param integer $ansi_width 汉字等非英文字符按照几个字符来处理
* @return string 返回裁减的字符串
*/
function substr_ansi($string, $offset, $length = 0, $ansi_width = 1)
{
if (empty($string)) return $string;;
$data = str_split_utf8($string);
if (empty($data)) return $string;
$as = $_as = array();
$_start = $_end = 0;
foreach($data as $k => $v)
$as[$k] = strlen($v) > 1 ? $ansi_width : 1;
$_as_rev = array_reverse($as,true);
$_as = $offset < 0 ? $_as_rev : $as;
$n = 0; $_offset = abs($offset);
foreach($_as as $k => $v) {
if ($n >= $_offset) {
$_start = $k;
break;
}
$n += $v;
}
//echo $_start,',';
$_as = $length <= 0 ? $_as_rev : $as;
end($_as); list($_end) = each($_as); reset($_as);//给$_end 设定默认值,一直到结尾
$n = 0; $_length = abs($length);
foreach($_as as $k => $v) {
if ($k >= $_start) {
if ($n >= $_length) {
$_end = $k + ($length <= 0 ? 1 : 0);
break;
}
$n += $v;
}
}
//echo $_end,'|||||';
if ($_end <= $_start)
return '';
$_data = array_slice($data, $_start, $_end - $_start);
return implode('',$_data);
}
/**
* 按非ascii字符占有几个字宽的方式计算字符串长度
* @example $ansi_width = 2 表示汉字等非英文字符按照两个字宽长度
* @example $ansi_width = 1 表示所有字符按一个字节长度
*
* @param string $string 原始字符
* @param integer $ansi_width 汉字等非英文字符按照几个字宽来处理
* @return string 返回字符串长度
*/
function strlen_ansi($string, $ansi_width = 1)
{
if (empty($string)) return 0;
$data = str_split_utf8($string);
if (empty($data)) return 0;
$as = 0;
foreach($data as $k => $v)
$as += strlen($v) > 1 ? $ansi_width : 1;
unset($data);
return $as;
}
/**
* smarty truncate 代码算法来自于Smarty
* @param string
* @param integer
* @param string
* @param boolean
* @param boolean
* @return string
*/
function truncate($string, $length = 80, $etc = '...', $break_words = false, $middle = false)
{
if ($length == 0)
return '';
$ansi_as = 2;
if (strlen_ansi($string, $ansi_as) > $length) {
$length -= min($length, strlen_ansi($etc, $ansi_as));
if (!$break_words && !$middle) {
$string = preg_replace('/\s+?(\S+)?$/u', '', substr_ansi($string, 0, $length+1, $ansi_as));
}
if(!$middle) {
return substr_ansi($string, 0, $length, $ansi_as) . $etc;
} else {
return substr_ansi($string, 0, $length/2, $ansi_as) . $etc . substr_ansi($string, -$length/2, 0, $ansi_as);
}
} else {
return $string;
}
}
substr_ansi
、 truncate
便是你要的截取的函数
// substr_ansi ($offset, $length, $ansi_width)
// 如果ansi_width = 2,则表示将汉字当做2个宽度处理
// offset length 在实际截取过程中,以英文的长度为准即可
echo substr_ansi('汉字我爱你', 0, 5, 2); //输出:汉字我
echo substr_ansi('汉字abc我爱你', 0, 5, 2); //输出:汉字a
echo substr_ansi('abcdef', 0, 5, 2); //输出:abcde
echo mb_substr('汉字我爱你', 0, 5); //输出:汉字我爱你
echo mb_substr('汉字abc我爱你', 0, 5); //输出:汉字abc
echo mb_substr('abcdef', 0, 5); //输出:abcde
可以看到上面substr_ansi的截取后的像素宽度是正确的,并且,汉字不会截取半个
下面的mb_substr长度明显不一致
针对日文、GBK、GB2312、Unicode等情况,请参见:
http://www.load-page.com:8989...
由于实在没有精力,以及答主不太懂日文(韩文),有些字符集的ASCII区域无法弄清楚,但是此代码在中文方面经过生产环境的验证,已经没有什么问题。有了解东亚,欧洲等字符集的同好,欢迎私信联系我。
string mb_strimwidth ( string $str , int $start , int $width [, string $trimmarker = "" [, string $encoding = mb_internal_encoding() ]] )
PHP提供的这个函数看起来可以满足你的要求. $trimmarker 是如果长度超了, 后面添加的...这三字符.
public static function mb_substr(&$str, $length, $encoding = 'utf-8')
{
return isset($str) ? mb_substr($str, 0, $length, $encoding) . ($length < mb_strlen($str, $encoding) ? '...' : '') : '';
}
4 回答13.2k 阅读✓ 已解决
5 回答7.7k 阅读✓ 已解决
1 回答1.5k 阅读✓ 已解决
1 回答950 阅读✓ 已解决
1 回答1.4k 阅读
1 回答1.4k 阅读
2 回答992 阅读
在
php
中编码是UTF-8
的话占3个字节;是GB2312
的话占2个字节。推荐你把所有字符设置成同一种编码字符处理。php
除了strlen
和substr
之外,还有带mb_
开头的啊!可以指定字符串编码格式例如mb_strlen和mb_substr试试看